You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:07:33