关于动态生成SQL语句的简单实现
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Linq.Expressions; 6 using System.Reflection; 7 using System.Text; 8 using System.Text.RegularExpressions; 9 10 namespace PostgreSqlBuilder 11 { 12 public class PostgreSqlBuilder 13 { 14 ///15 /// 模式 16 /// 17 public string Schema { get; set; } 18 19 public PostgreSqlBuilder() { } 20 public PostgreSqlBuilder(string schema) 21 { 22 this.Schema = schema; 23 } 24 25 /// 26 /// 批量生成INSERT SQL脚本-无自增外键 27 /// 28 /// 目标对象类型 29 /// 目标对象集合 30 /// 目标对象主键名 31 /// 32 public string BatchInsertSql (IEnumerable collection, string key = "Id") where T : class 33 { 34 var sbBatch = new StringBuilder(); 35 foreach (var obj in collection) 36 { 37 sbBatch.AppendLine(InsertSqlNoIncForeignKey(obj, key)); 38 } 39 return sbBatch.ToString(); 40 } 41 /// 42 /// 批量生成INSERT SQL脚本-有一个自增外键; 43 /// 需先生成主键表sql脚本插入成功后,再使用该方法; 44 /// 要求参数constraint为主外键表共有字段; 45 /// 46 /// 目标对象类型 47 /// 外键关联对象类型 48 /// 目标对象集合 49 /// 目标对象外键名 50 /// 外键关联对象唯一约束属性名(非主键) 51 /// INSERT SQL脚本 52 public string BatchInsertSqlWithOuterKeyId (IEnumerable collection 53 , string outKey, string constraint) where T : class where TOuter : class 54 { 55 var sbBatch = new StringBuilder(); 56 foreach (var obj in collection) 57 { 58 sbBatch.AppendLine(InsertSqlWithOuterKeyId (obj, outKey, constraint)); 59 } 60 return sbBatch.ToString(); 61 } 62 /// 63 /// 批量生成UPDATE-INSERT SQL脚本 64 /// 65 /// 目标对象类型 66 /// 目标对象集合 67 /// 目标对象主键名 68 /// 冲突 69 /// 70 public string BatchUpSertSql (IEnumerable collection, string key = "Id", params string[] conflicts) where T : class 71 { 72 var sbBatch = new StringBuilder(); 73 foreach (var obj in collection) 74 { 75 sbBatch.AppendLine(UpSertSql(obj, key, conflicts)); 76 } 77 return sbBatch.ToString(); 78 } 79 80 #region 生成INSERT SQL脚本-无自增外键 81 82 /// 83 /// 生成INSERT SQL脚本-无自增外键 84 /// 85 /// 目标对象类型 86 /// 目标对象 87 /// 目标对象主键名 88 /// INSERT SQL脚本 89 public string InsertSqlNoIncForeignKey (T targetObj, string targetObjKey = "Id") where T : class 90 { 91 var type = typeof(T); 92 var tableName = GetTableName(type); 93 var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) 94 .Where(p => IsFundamental(p.PropertyType)); 95 var sbColumn = new StringBuilder(100); 96 var sbValue = new StringBuilder(200); 97 sbColumn.Append("INSERT INTO " + tableName + " ("); 98 sbValue.Append(" VALUES ("); 99 foreach (var propertyInfo in propertyInfos) 100 { 101 //自增主键(默认int类型),过滤 102 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue; 103 sbColumn.Append($" {GetFieldName(propertyInfo)}"); 104 sbColumn.Append(" ,"); 105 //获取属性值 106 var attribute = 107 propertyInfo.GetCustomAttribute(typeof(SetPostgreSqlValueAttribute), false); 108 var propertyValue = propertyInfo.GetValue(targetObj); 109 if (attribute != null) 110 { 111 if (attribute is SetPostgreSqlValueAttribute setSqlValueAttribute) 112 sbValue.Append($"{setSqlValueAttribute.Value} ,"); 113 } 114 else if (propertyValue == null) 115 sbValue.Append(" null,"); 116 else 117 { 118 sbValue.Append(" '"); 119 sbValue.Append($"{propertyValue?.ToString()}"); 120 sbValue.Append("' ,"); 121 } 122 } 123 sbColumn.Replace(',', ')', sbColumn.Length - 1, 1); 124 sbValue.Replace(',', ')', sbValue.Length - 1, 1); 125 sbColumn.Append(sbValue).Append(';'); 126 return sbColumn.ToString(); 127 } 128 129 /// 130 /// 生成INSERT SQL脚本-无自增外键 131 /// 132 /// 目标对象类型 133 /// 目标对象 134 /// 目标对象属性表达式-主键 135 /// INSERT SQL脚本 136 public string InsertSqlNoIncForeignKey (T targetObj, Expression > targetObjKeyExpr) where T : class 137 { 138 var type = typeof(T); 139 var tableName = GetTableName(type); 140 var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) 141 .Where(p => IsFundamental(p.PropertyType)); 142 143 if (targetObjKeyExpr.Body is not MemberExpression body) 144 throw new ArgumentException($"'targetObjKeyExpr'不是MemberExpression, 表达式:{targetObjKeyExpr}"); 145 var keyPropertyInfo = (PropertyInfo)body.Member; 146 147 var sbColumn = new StringBuilder(100); 148 var sbValue = new StringBuilder(200); 149 sbColumn.Append("INSERT INTO " + tableName + " ("); 150 sbValue.Append(" VALUES ("); 151 foreach (var propertyInfo in propertyInfos) 152 { 153 //自增主键(默认int类型),过滤 154 if (propertyInfo.Name.Equals(keyPropertyInfo.Name) && 155 keyPropertyInfo.PropertyType == typeof(int)) continue; 156 sbColumn.Append($" {GetFieldName(propertyInfo)}"); 157 sbColumn.Append(" ,"); 158 //获取属性值 159 var attribute = 160 propertyInfo.GetCustomAttribute(typeof(SetPostgreSqlValueAttribute), false); 161 var propertyValue = propertyInfo.GetValue(targetObj); 162 if (attribute != null) 163 { 164 if (attribute is SetPostgreSqlValueAttribute setSqlValueAttribute) 165 sbValue.Append($"{setSqlValueAttribute.Value} ,"); 166 } 167 else if (propertyValue == null) 168 sbValue.Append(" null,"); 169 else 170 { 171 sbValue.Append(" '"); 172 sbValue.Append($"{propertyValue?.ToString()}"); 173 sbValue.Append("' ,"); 174 } 175 } 176 sbColumn.Replace(',', ')', sbColumn.Length - 1, 1); 177 sbValue.Replace(',', ')', sbValue.Length - 1, 1); 178 sbColumn.Append(sbValue).Append(';'); 179 return sbColumn.ToString(); 180 } 181 182 #endregion 183 184 /// 185 /// 生成INSERT SQL脚本-有一个自增外键; 186 /// 需先生成主键表sql脚本插入成功后,再使用该方法; 187 /// 要求参数constraint为主外键表共有字段; 188 /// 189 /// 目标对象类型 190 /// 外键关联对象类型 191 /// 目标对象 192 /// 目标对象外键名 193 /// 外键关联对象唯一约束属性名(非主键) 194 /// 目标对象主键名 195 /// 外键关联对象主键名 196 /// INSERT SQL脚本 197 public string InsertSqlWithOuterKeyId (T targetObj, string foreignKey, string constraint, string targetObjKey = "Id", string foreignObjKey = "Id") where T : class where TForeign : class 198 { 199 var type = typeof(T); 200 var tableName = GetTableName(typeof(T)); 201 var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); 202 var sbColumn = new StringBuilder(100); 203 var sbValue = new StringBuilder(200); 204 sbColumn.Append("INSERT INTO " + tableName + " ("); 205 sbValue.Append(" VALUES ("); 206 foreach (var propertyInfo in propertyInfos) 207 { 208 //自增主键(默认int类型),过滤 209 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue; 210 sbColumn.Append($" {GetFieldName(propertyInfo)}"); 211 sbColumn.Append(" ,"); 212 //获取属性值 213 if (propertyInfo.Name.Equals(foreignKey)) 214 { 215 sbValue.Append( 216 @$" (SELECT {ObjectTableNameConvention(foreignObjKey)} FROM {GetTableName(typeof(TForeign))} WHERE {ObjectTableNameConvention(constraint)}='{typeof(T).GetProperty(constraint)?.GetValue(targetObj)}'),"); 217 continue; 218 } 219 220 var setSqlValueAttributes = 221 propertyInfo.GetCustomAttributes(typeof(SetPostgreSqlValueAttribute), false); 222 if (setSqlValueAttributes.Length > 0) 223 { 224 var setSqlValueAttribute = setSqlValueAttributes[0] as SetPostgreSqlValueAttribute; 225 sbValue.Append($"{setSqlValueAttribute?.Value} ,"); 226 } 227 else if (propertyInfo.GetValue(targetObj) == null) 228 { 229 sbValue.Append(" null,"); 230 } 231 else 232 { 233 sbValue.Append(" '"); 234 sbValue.Append($"{propertyInfo.GetValue(targetObj)}"); 235 sbValue.Append("' ,"); 236 } 237 } 238 sbColumn.Replace(',', ')', sbColumn.Length - 1, 1); 239 sbValue.Replace(',', ')', sbValue.Length - 1, 1); 240 sbColumn.Append(sbValue).Append(';'); 241 return sbColumn.ToString(); 242 } 243 /// 244 /// 生成UPDATE-INSERT SQL脚本 245 /// 246 /// 目标对象类型 247 /// 目标对象 248 /// 目标对象主键名 249 /// 冲突 250 /// UPDATE-INSERT SQL脚本 251 public string UpSertSql (T targetObj, string targetObjKey = "Id", params string[] conflicts) where T : class 252 { 253 var type = typeof(T); 254 var tableName = GetTableName(typeof(T)); 255 var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); 256 var sbColumn = new StringBuilder(100); 257 var sbValue = new StringBuilder(200); 258 var sbConflict = new StringBuilder(100); 259 sbColumn.Append("INSERT INTO " + tableName + " ("); 260 sbValue.Append(" VALUES ("); 261 sbConflict.Append(" ON CONFLICT("); 262 foreach (var conflict in conflicts) 263 { 264 if (!propertyInfos.Select(p => p.Name).Contains(conflict)) 265 { 266 throw new ArgumentNullException($"{typeof(T).Name}不存在字段名{conflict}"); 267 } 268 sbConflict.Append($" {ObjectTableNameConvention(conflict)},"); 269 } 270 sbConflict.Replace(',', ')', sbConflict.Length - 1, 1).Append(" DO UPDATE SET"); 271 foreach (var propertyInfo in propertyInfos) 272 { 273 //自增主键(默认int类型),过滤 274 if (propertyInfo.Name.Equals(targetObjKey) && propertyInfo.PropertyType == typeof(int)) continue; 275 sbColumn.Append($" {GetFieldName(propertyInfo)}"); 276 sbColumn.Append(" ,"); 277 //获取属性值 278 var value = string.Empty; 279 var setSqlValueAttributes = 280 propertyInfo.GetCustomAttributes(typeof(SetPostgreSqlValueAttribute), false); 281 if (setSqlValueAttributes.Length > 0) 282 { 283 if (setSqlValueAttributes[0] is SetPostgreSqlValueAttribute setSqlValueAttribute) 284 { 285 value = $"{setSqlValueAttribute.Value} ,"; 286 sbValue.Append(value); 287 } 288 } 289 else if (propertyInfo.GetValue(targetObj) == null) 290 { 291 value = " null,"; 292 sbValue.Append(value); 293 } 294 else 295 { 296 value = " '" + $"{propertyInfo.GetValue(targetObj)}" + "' ,"; 297 sbValue.Append(value); 298 } 299 sbConflict.Append($" {GetFieldName(propertyInfo)}={value}"); 300 } 301 sbColumn.Replace(',', ')', sbColumn.Length - 1, 1); 302 sbValue.Replace(',', ')', sbValue.Length - 1, 1); 303 sbConflict.Remove(sbConflict.Length - 1, 1); 304 sbColumn.Append(sbValue).Append(sbConflict).Append(';'); 305 return sbColumn.ToString(); 306 } 307 308 #region 内部方法 309 310 /// 311 /// 获取表名 312 /// 313 /// 类型对象 314 /// 表名 315 protected string GetTableName(Type type) 316 { 317 var className = type.Name; 318 return string.IsNullOrWhiteSpace(this.Schema) 319 ? ObjectTableNameConvention(className) 320 : ObjectTableNameConvention(this.Schema) + "." + ObjectTableNameConvention(className); 321 } 322 323 /// 324 /// 获取表字段名 325 /// 326 /// 对象属性信息 327 /// 表字段名 328 protected string GetFieldName(PropertyInfo propertyInfo) 329 { 330 return ObjectTableNameConvention(propertyInfo.Name); 331 } 332 333 /// 334 /// 对象-表 名称转换 335 /// 336 /// 对象中名称 337 /// 表中名称 338 protected string ObjectTableNameConvention(string objectName) 339 { 340 var pattern = 341 new Regex(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+"); 342 var snakeCaseName = objectName == null 343 ? null 344 : string 345 .Join("_", pattern.Matches(objectName).Cast ().Select(m => m.Value)) 346 .ToLower(); 347 return snakeCaseName; 348 } 349 350 /// 351 /// 是否基础类型(值类型,枚举,字符串) 352 /// 353 /// 354 /// 355 protected bool IsFundamental(Type type) 356 { 357 return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(DateTime); 358 } 359 360 #endregion 361 362 } 363 }
测试下性能和结果:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace PostgreSqlBuilder 6 { 7 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 8 public class SetPostgreSqlValueAttribute : Attribute 9 { 10 private readonly SqlValueType _type; 11 private readonly string _value; 12 13 public SetPostgreSqlValueAttribute(SqlValueType type, string value) 14 { 15 _type = type; 16 _value = value; 17 } 18 public string Value 19 { 20 get 21 { 22 return _type switch 23 { 24 SqlValueType.Constant => "'" + _value + "'", 25 SqlValueType.Function or SqlValueType.KeyWord => _value, 26 _ => throw new NotSupportedException($"_type值错误{_type}"), 27 }; 28 } 29 } 30 } 31 }
结果:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace PostgreSqlBuilder 6 { 7 public enum SqlValueType 8 { 9 ///10 /// slq常量 11 /// 12 Constant = 0, 13 /// 14 /// sql方法 15 /// 16 Function = 1, 17 /// 18 /// sql关键字 19 /// 20 KeyWord = 2 21 } 22 }