如何在Dapper通用仓储中动态生成SQL的INSERT与UPDATE查询?
在Dapper通用仓储中动态生成INSERT/UPDATE SQL查询
可以通过反射+结果缓存实现任意实体的动态SQL生成,彻底摆脱硬编码属性的限制。下面是具体实现方案:
一、基础准备:缓存反射结果
反射操作开销较高,提前缓存实体的属性元数据,避免每次执行SQL都重复反射:
using System.Reflection; using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations; public static class EntityMetadataCache { private static readonly Dictionary<Type, EntityMetadata> _cache = new(); public static EntityMetadata GetMetadata<T>() { var type = typeof(T); if (_cache.TryGetValue(type, out var metadata)) return metadata; // 筛选出非NotMapped的公开实例属性 var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !Attribute.IsDefined(p, typeof(NotMappedAttribute))) .ToList(); // 识别主键:优先标记[Key]特性的属性,其次匹配名称为Id的属性 var primaryKey = properties.FirstOrDefault(p => Attribute.IsDefined(p, typeof(KeyAttribute)) || p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)); // 映射数据库列名:支持[Column]特性,无特性则用属性名 var columnNames = properties.ToDictionary( p => p.Name, p => Attribute.GetCustomAttribute(p, typeof(ColumnAttribute)) is ColumnAttribute col ? col.Name : p.Name); // 映射表名:支持[Table]特性,无特性则用实体类名 var tableName = Attribute.GetCustomAttribute(type, typeof(TableAttribute)) is TableAttribute table ? table.Name : type.Name; metadata = new EntityMetadata { TableName = tableName, Properties = properties, PrimaryKey = primaryKey, ColumnNames = columnNames }; _cache[type] = metadata; return metadata; } public class EntityMetadata { public string TableName { get; set; } public List<PropertyInfo> Properties { get; set; } public PropertyInfo PrimaryKey { get; set; } public Dictionary<string, string> ColumnNames { get; set; } } }
二、动态生成INSERT语句
利用缓存的元数据,自动拼接INSERT的字段和参数:
public async Task<int> AddAsync<T>(T entity) { var metadata = EntityMetadataCache.GetMetadata<T>(); // 排除自增主键(无需插入) var insertProperties = metadata.Properties.Where(p => p != metadata.PrimaryKey).ToList(); var columns = string.Join(", ", insertProperties.Select(p => metadata.ColumnNames[p.Name])); var parameters = string.Join(", ", insertProperties.Select(p => $"@{p.Name}")); var sql = $"INSERT INTO {metadata.TableName} ({columns}) VALUES ({parameters})"; using (var dbConnection = new NpgsqlConnection(_connectionString)) { await dbConnection.OpenAsync(); return await dbConnection.ExecuteAsync(sql, entity); } }
三、动态生成UPDATE语句
定位主键作为WHERE条件,自动生成SET子句:
public async Task<int> UpdateAsync<T>(T entity) { var metadata = EntityMetadataCache.GetMetadata<T>(); if (metadata.PrimaryKey == null) throw new InvalidOperationException($"实体{typeof(T).Name}未定义主键"); // 生成SET子句:列名=参数名 var setClauses = metadata.Properties .Where(p => p != metadata.PrimaryKey) .Select(p => $"{metadata.ColumnNames[p.Name]} = @{p.Name}") .ToList(); var sql = $"UPDATE {metadata.TableName} SET {string.Join(", ", setClauses)} WHERE {metadata.ColumnNames[metadata.PrimaryKey.Name]} = @{metadata.PrimaryKey.Name}"; using (var dbConnection = new NpgsqlConnection(_connectionString)) { await dbConnection.OpenAsync(); return await dbConnection.ExecuteAsync(sql, entity); } }
四、关键注意事项
- SQL注入防护:Dapper的参数化查询会自动处理参数安全,拼接的表名/列名来自实体元数据(而非用户输入),无需额外防护。
- 数据库兼容性:若需适配多数据库(如SQL Server、MySQL),可扩展元数据缓存,添加数据库特定语法处理(比如SQL Server的
SCOPE_IDENTITY()返回主键)。 - 特性扩展:代码已支持EF常用特性,可根据需求添加其他自定义特性的处理逻辑。
内容的提问来源于stack exchange,提问作者Abhi Kumar Prince
相关产品推荐
相关产品推荐

