关于动态生成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语句的简单实现的主要内容,如果未能解决你的问题,请参考以下文章
真正的Mybatis动态sql —MyBatis Dynamic SQL
--------------------------------MaBatis动态sql--------------------------