关于动态生成SQL语句的简单实现

Posted SHao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于动态生成SQL语句的简单实现相关的知识,希望对你有一定的参考价值。

  

  代码如下:

 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;
 using System.Text;
 using System.Text.RegularExpressions;
 
 namespace PostgreSqlBuilder
 
     public class PostgreSqlBuilder
     
         /// <summary>
         /// 模式
         /// </summary>
         public string Schema  get; set; 
         
         public PostgreSqlBuilder()  
         public PostgreSqlBuilder(string schema)
         
             this.Schema = schema;
         
 
         /// <summary>
         /// 批量生成INSERT SQL脚本-无自增外键
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <param name="collection">目标对象集合</param>
         /// <param name="key">目标对象主键名</param>
         /// <returns></returns>
         public string BatchInsertSql<T>(IEnumerable<T> collection, string key = "Id") where T : class
         
             var sbBatch = new StringBuilder();
             foreach (var obj in collection)
             
                 sbBatch.AppendLine(InsertSqlNoIncForeignKey(obj, key));
             
             return sbBatch.ToString();
         
         /// <summary>
         /// 批量生成INSERT SQL脚本-有一个自增外键;
         /// 需先生成主键表sql脚本插入成功后,再使用该方法;
         /// 要求参数constraint为主外键表共有字段;
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <typeparam name="TOuter">外键关联对象类型</typeparam>
         /// <param name="collection">目标对象集合</param>
         /// <param name="outKey">目标对象外键名</param>
         /// <param name="constraint">外键关联对象唯一约束属性名(非主键)</param>
         /// <returns>INSERT SQL脚本</returns>
         public string BatchInsertSqlWithOuterKeyId<T,TOuter>(IEnumerable<T> collection
             , string outKey, string constraint) where T : class where TOuter : class
         
             var sbBatch = new StringBuilder();
             foreach (var obj in collection)
             
                 sbBatch.AppendLine(InsertSqlWithOuterKeyId<T,TOuter>(obj, outKey, constraint));
             
             return sbBatch.ToString();
         
         /// <summary>
         /// 批量生成UPDATE-INSERT SQL脚本
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <param name="collection">目标对象集合</param>
         /// <param name="key">目标对象主键名</param>
         /// <param name="conflicts">冲突</param>
         /// <returns></returns>
         public string BatchUpSertSql<T>(IEnumerable<T> collection, string key = "Id", params string[] conflicts) where T : class
         
             var sbBatch = new StringBuilder();
             foreach (var obj in collection)
             
                 sbBatch.AppendLine(UpSertSql(obj, key, conflicts));
             
             return sbBatch.ToString();
         
 
         #region 生成INSERT SQL脚本-无自增外键
 
         /// <summary>
         /// 生成INSERT SQL脚本-无自增外键
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <param name="targetObj">目标对象</param>
         /// <param name="targetObjKey">目标对象主键名</param>
         /// <returns>INSERT SQL脚本</returns>
         public string InsertSqlNoIncForeignKey<T>(T targetObj, string targetObjKey = "Id") where T : class
         
             var type = typeof(T);
             var tableName = GetTableName(type);
             var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                 .Where(p => IsFundamental(p.PropertyType));
             var sbColumn = new StringBuilder(100);
             var sbValue = new StringBuilder(200);
             sbColumn.Append("INSERT INTO " + tableName + " (");
             sbValue.Append(" VALUES (");
             foreach (var propertyInfo in propertyInfos)
             
                 //自增主键(默认int类型),过滤
                 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue;
                 sbColumn.Append($" GetFieldName(propertyInfo)");
                 sbColumn.Append(" ,");
                 //获取属性值
                 var attribute =
                     propertyInfo.GetCustomAttribute(typeof(SetPostgreSqlValueAttribute), false);
                 var propertyValue = propertyInfo.GetValue(targetObj);
                 if (attribute != null)
                 
                     if (attribute is SetPostgreSqlValueAttribute setSqlValueAttribute)
                         sbValue.Append($"setSqlValueAttribute.Value ,");
                 
                 else if (propertyValue == null)
                     sbValue.Append(" null,");
                 else
                 
                     sbValue.Append(" \'");
                     sbValue.Append($"propertyValue?.ToString()");
                     sbValue.Append("\' ,");
                 
             
             sbColumn.Replace(\',\', \')\', sbColumn.Length - 1, 1);
             sbValue.Replace(\',\', \')\', sbValue.Length - 1, 1);
             sbColumn.Append(sbValue).Append(\';\');
             return sbColumn.ToString();
         
 
         /// <summary>
         /// 生成INSERT SQL脚本-无自增外键
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <param name="targetObj">目标对象</param>
         /// <param name="targetObjKeyExpr">目标对象属性表达式-主键</param>
         /// <returns>INSERT SQL脚本</returns>
         public string InsertSqlNoIncForeignKey<T, TK>(T targetObj, Expression<Func<T, TK>> targetObjKeyExpr) where T : class
         
             var type = typeof(T);
             var tableName = GetTableName(type);
             var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                 .Where(p => IsFundamental(p.PropertyType));
 
             if (targetObjKeyExpr.Body is not MemberExpression body)
                 throw new ArgumentException($"\'targetObjKeyExpr\'不是MemberExpression, 表达式:targetObjKeyExpr");
             var keyPropertyInfo = (PropertyInfo)body.Member;
 
             var sbColumn = new StringBuilder(100);
             var sbValue = new StringBuilder(200);
             sbColumn.Append("INSERT INTO " + tableName + " (");
             sbValue.Append(" VALUES (");
             foreach (var propertyInfo in propertyInfos)
             
                 //自增主键(默认int类型),过滤
                 if (propertyInfo.Name.Equals(keyPropertyInfo.Name) &&
                     keyPropertyInfo.PropertyType == typeof(int)) continue;
                 sbColumn.Append($" GetFieldName(propertyInfo)");
                 sbColumn.Append(" ,");
                 //获取属性值
                 var attribute =
                     propertyInfo.GetCustomAttribute(typeof(SetPostgreSqlValueAttribute), false);
                 var propertyValue = propertyInfo.GetValue(targetObj);
                 if (attribute != null)
                 
                     if (attribute is SetPostgreSqlValueAttribute setSqlValueAttribute)
                         sbValue.Append($"setSqlValueAttribute.Value ,");
                 
                 else if (propertyValue == null)
                     sbValue.Append(" null,");
                 else
                 
                     sbValue.Append(" \'");
                     sbValue.Append($"propertyValue?.ToString()");
                     sbValue.Append("\' ,");
                 
             
             sbColumn.Replace(\',\', \')\', sbColumn.Length - 1, 1);
             sbValue.Replace(\',\', \')\', sbValue.Length - 1, 1);
             sbColumn.Append(sbValue).Append(\';\');
             return sbColumn.ToString();
          
 
         #endregion
 
         /// <summary>
         /// 生成INSERT SQL脚本-有一个自增外键;
         /// 需先生成主键表sql脚本插入成功后,再使用该方法;
         /// 要求参数constraint为主外键表共有字段;
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <typeparam name="TForeign">外键关联对象类型</typeparam>
         /// <param name="targetObj">目标对象</param>
         /// <param name="foreignKey">目标对象外键名</param>
         /// <param name="constraint">外键关联对象唯一约束属性名(非主键)</param>
         /// <param name="targetObjKey">目标对象主键名</param>
         /// <param name="foreignObjKey">外键关联对象主键名</param>
         /// <returns>INSERT SQL脚本</returns>
         public string InsertSqlWithOuterKeyId<T, TForeign>(T targetObj, string foreignKey, string constraint, string targetObjKey = "Id", string foreignObjKey = "Id") where T : class where TForeign : class
         
             var type = typeof(T);
             var tableName = GetTableName(typeof(T));
             var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
             var sbColumn = new StringBuilder(100);
             var sbValue = new StringBuilder(200);
             sbColumn.Append("INSERT INTO " + tableName + " (");
             sbValue.Append(" VALUES (");
             foreach (var propertyInfo in propertyInfos)
             
                 //自增主键(默认int类型),过滤
                 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue;
                 sbColumn.Append($" GetFieldName(propertyInfo)");
                 sbColumn.Append(" ,");
                 //获取属性值
                 if (propertyInfo.Name.Equals(foreignKey))
                 
                     sbValue.Append(
                         @$" (SELECT ObjectTableNameConvention(foreignObjKey) FROM GetTableName(typeof(TForeign)) WHERE ObjectTableNameConvention(constraint)=\'typeof(T).GetProperty(constraint)?.GetValue(targetObj)\'),");
                     continue;
                 
 
                 var setSqlValueAttributes =
                     propertyInfo.GetCustomAttributes(typeof(SetPostgreSqlValueAttribute), false);
                 if (setSqlValueAttributes.Length > 0)
                 
                     var setSqlValueAttribute = setSqlValueAttributes[0] as SetPostgreSqlValueAttribute;
                     sbValue.Append($"setSqlValueAttribute?.Value ,");
                 
                 else if (propertyInfo.GetValue(targetObj) == null)
                 
                     sbValue.Append(" null,");
                 
                 else
                 
                     sbValue.Append(" \'");
                     sbValue.Append($"propertyInfo.GetValue(targetObj)");
                     sbValue.Append("\' ,");
                 
             
             sbColumn.Replace(\',\', \')\', sbColumn.Length - 1, 1);
             sbValue.Replace(\',\', \')\', sbValue.Length - 1, 1);
             sbColumn.Append(sbValue).Append(\';\');
             return sbColumn.ToString();
         
         /// <summary>
         /// 生成UPDATE-INSERT SQL脚本
         /// </summary>
         /// <typeparam name="T">目标对象类型</typeparam>
         /// <param name="targetObj">目标对象</param>
         /// <param name="targetObjKey">目标对象主键名</param>
         /// <param name="conflicts">冲突</param>
         /// <returns>UPDATE-INSERT SQL脚本</returns>
         public string UpSertSql<T>(T targetObj, string targetObjKey = "Id", params string[] conflicts) where T : class
         
             var type = typeof(T);
             var tableName = GetTableName(typeof(T));
             var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
             var sbColumn = new StringBuilder(100);
             var sbValue = new StringBuilder(200);
             var sbConflict = new StringBuilder(100);
             sbColumn.Append("INSERT INTO " + tableName + " (");
             sbValue.Append(" VALUES (");
             sbConflict.Append(" ON CONFLICT(");
             foreach (var conflict in conflicts)
             
                 if (!propertyInfos.Select(p => p.Name).Contains(conflict))
                 
                     throw new ArgumentNullException($"typeof(T).Name不存在字段名conflict");
                 
                 sbConflict.Append($" ObjectTableNameConvention(conflict),");
             
             sbConflict.Replace(\',\', \')\', sbConflict.Length - 1, 1).Append(" DO UPDATE SET");
             foreach (var propertyInfo in propertyInfos)
             
                 //自增主键(默认int类型),过滤
                 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue;
                 sbColumn.Append($" GetFieldName(propertyInfo)");
                 sbColumn.Append(" ,");
                 //获取属性值
                 var value = string.Empty;
                 var setSqlValueAttributes =
                     propertyInfo.GetCustomAttributes(typeof(SetPostgreSqlValueAttribute), false);
                 if (setSqlValueAttributes.Length > 0)
                 
                     if (setSqlValueAttributes[0] is SetPostgreSqlValueAttribute setSqlValueAttribute)
                     
                         value = $"setSqlValueAttribute.Value ,";
                         sbValue.Append(value);
                     
                 
                 else if (propertyInfo.GetValue(targetObj) == null)
                 
                     value = " null,";
                     sbValue.Append(value);
                 
                 else
                 
                     value = " \'" + $"propertyInfo.GetValue(targetObj)" + "\' ,";
                     sbValue.Append(value);
                 
                 sbConflict.Append($" GetFieldName(propertyInfo)=value");
             
             sbColumn.Replace(\',\', \')\', sbColumn.Length - 1, 1);
             sbValue.Replace(\',\', \')\', sbValue.Length - 1, 1);
             sbConflict.Remove(sbConflict.Length - 1, 1);
             sbColumn.Append(sbValue).Append(sbConflict).Append(\';\');
             return sbColumn.ToString();
         
 
         #region 内部方法
 
         /// <summary>
         /// 获取表名
         /// </summary>
         /// <param name="type">类型对象</param>
         /// <returns>表名</returns>
         protected string GetTableName(Type type)
         
             var className = type.Name;
             return string.IsNullOrWhiteSpace(this.Schema)
                 ? ObjectTableNameConvention(className)
                 : ObjectTableNameConvention(this.Schema) + "." + ObjectTableNameConvention(className);
         
 
         /// <summary>
         /// 获取表字段名
         /// </summary>
         /// <param name="propertyInfo">对象属性信息</param>
         /// <returns>表字段名</returns>
         protected string GetFieldName(PropertyInfo propertyInfo)
         
             return ObjectTableNameConvention(propertyInfo.Name);
         
 
         /// <summary>
         /// 对象-表 名称转换
         /// </summary>
         /// <param name="objectName">对象中名称</param>
         /// <returns>表中名称</returns>
         protected string ObjectTableNameConvention(string objectName)
         
             var pattern =
                 new Regex(@"[A-Z]2,(?=[A-Z][a-z]+[0-9]*|\\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+");
             var snakeCaseName = objectName == null
                    ? null
                    : string
                        .Join("_", pattern.Matches(objectName).Cast<Match>().Select(m => m.Value))
                        .ToLower();
             return snakeCaseName;
         
 
         /// <summary>
         /// 是否基础类型(值类型,枚举,字符串)
         /// </summary>
         /// <param name="type"></param>
         /// <returns></returns>
         protected bool IsFundamental(Type type)
         
             return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(DateTime);
          
 
         #endregion
 
     
 

  测试下性能和结果:

 using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace PostgreSqlBuilder
 
     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
     public class SetPostgreSqlValueAttribute : Attribute
     
         private readonly SqlValueType _type;
         private readonly string _value;
 
         public SetPostgreSqlValueAttribute(SqlValueType type, string value)
         
             _type = type;
             _value = value;
         
         public string Value
         
             get
             
                 return _type switch
                 
                     SqlValueType.Constant => "\'" + _value + "\'",
                     SqlValueType.Function or SqlValueType.KeyWord => _value,
                     _ => throw new NotSupportedException($"_type值错误_type"),
                 ;
             
         
     
 

  结果:

 using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace PostgreSqlBuilder
 
     public enum SqlValueType
     
         /// <summary>
         /// slq常量
         /// </summary>
         Constant = 0,
         /// <summary>
         /// sql方法
         /// </summary>
         Function = 1,
         /// <summary>
         /// sql关键字
         /// </summary>
         KeyWord = 2
     
 

 

以上是关于关于动态生成SQL语句的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

如何生成ibatis 动态sql

mybatis动态sql语句问题

SQL Server 动态生成数据库所有表Insert语句

真正的Mybatis动态sql —MyBatis Dynamic SQL

三:动态SQL

--------------------------------MaBatis动态sql--------------------------