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

Dapper如何映射列名与类属性名不一致的字段?(Oracle场景)

Dapper中关联查询列别名与实体属性的自动映射方案

针对关联查询列别名与C#实体属性名不匹配、且需要聚合一对多结果的问题,以下是几种可行的解决方案,其中MultiMap结合结果分组是最贴合场景的最佳方案:

1. 自定义类型映射(全局生效)

如果希望全局统一处理列名与属性名的映射关系,可以通过Dapper的DefaultTypeMap来配置:

// 可选:开启下划线命名转帕斯卡/驼峰命名的自动匹配
DefaultTypeMap.MatchNamesWithUnderscores = true;

// 配置Customer类的列映射规则
var customerTypeMap = new CustomPropertyTypeMap(typeof(Customer), (type, columnName) =>
{
    return columnName switch
    {
        "customer_id" => type.GetProperty(nameof(Customer.ID)),
        "customer_name" => type.GetProperty(nameof(Customer.Name)),
        // 默认按忽略大小写匹配属性
        _ => type.GetProperty(columnName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
    };
});

// 配置CustomerOrder类的列映射规则
var orderTypeMap = new CustomPropertyTypeMap(typeof(CustomerOrder), (type, columnName) =>
{
    return columnName switch
    {
        "customer_order_id" => type.GetProperty(nameof(CustomerOrder.ID)),
        "customer_order_name" => type.GetProperty(nameof(CustomerOrder.Name)),
        _ => type.GetProperty(columnName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
    };
});

// 注册映射规则
SqlMapper.SetTypeMap(typeof(Customer), customerTypeMap);
SqlMapper.SetTypeMap(typeof(CustomerOrder), orderTypeMap);

配置完成后,执行查询时Dapper会自动按照定义的规则完成列名到属性的映射。

2. 使用MultiMap手动映射并聚合一对多结果

由于场景是一对多关联(一个Customer对应多个CustomerOrder),直接映射会生成重复的Customer数据,因此推荐用Dapper的QueryMultiMap方法配合字典分组,同时手动指定列与属性的映射关系:

using (var connection = new OracleConnection("你的数据库连接字符串"))
{
    var sql = @"select      
        a.id customer_id, a.name customer_name, 
        b.id customer_order_id, b.name customer_order_name
    from        
        customer a     
    inner join      
        customer_order b on  a.id = b.customer_id
    order by    
        a.id, b.id";

    var customerDict = new Dictionary<int, Customer>();

    connection.Query<Customer, CustomerOrder, Customer>(
        sql,
        (customer, order) =>
        {
            // 按Customer的ID分组,避免重复实例
            if (!customerDict.TryGetValue(customer.ID, out var existingCustomer))
            {
                existingCustomer = customer;
                customerDict.Add(existingCustomer.ID, existingCustomer);
            }
            existingCustomer.CustomerOrders.Add(order);
            return existingCustomer;
        },
        splitOn: "customer_order_id" // 指定分割列,告知Dapper从该列开始映射第二个实体
    );

    // 最终得到去重且包含订单集合的Customer列表
    var customers = customerDict.Values.ToList();
}

核心逻辑说明:

  • splitOn参数指定分割列(customer_order_id),让Dapper明确前半段列映射到Customer,后半段映射到CustomerOrder
  • 用字典分组,将同一个Customer的多个Order聚合到CustomerOrders集合中,避免生成重复的Customer实例

3. 让ColumnAttribute生效的正确方式

如果坚持使用System.ComponentModel.DataAnnotations.Schema.ColumnAttribute,需要手动配置Dapper的类型映射来识别该特性:

public class ColumnAttributeTypeMap : DefaultTypeMap
{
    public ColumnAttributeTypeMap(Type type) : base(type) { }

    protected override PropertyInfo GetProperty(Type type, string columnName)
    {
        // 优先查找带有ColumnAttribute的属性
        foreach (var prop in type.GetProperties())
        {
            var columnAttr = prop.GetCustomAttributes(typeof(ColumnAttribute), false)
                                 .Cast<ColumnAttribute>()
                                 .FirstOrDefault();
            if (columnAttr != null && columnAttr.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase))
            {
                return prop;
            }
        }
        // 找不到匹配则使用默认规则
        return base.GetProperty(type, columnName);
    }
}

// 给实体类注册自定义类型映射
SqlMapper.SetTypeMap(typeof(Customer), new ColumnAttributeTypeMap(typeof(Customer)));
SqlMapper.SetTypeMap(typeof(CustomerOrder), new ColumnAttributeTypeMap(typeof(CustomerOrder)));

之后在实体类上添加ColumnAttribute即可:

public class Customer
{
    [Column("customer_id")]
    public int ID { get; set; }
    [Column("customer_name")]
    public string? Name { get; set; }

    public List<CustomerOrder> CustomerOrders { get; set; } = new List<CustomerOrder>();
}

public class CustomerOrder
{
    [Column("customer_order_id")]
    public int ID { get; set; }
    [Column("customer_order_name")]
    public string? Name { get; set; }
}

最佳方案推荐

优先选择MultiMap结合结果分组的方案,原因如下:

  • 同时解决了列名映射和一对多结果聚合的核心需求,无需额外全局配置
  • 代码逻辑清晰直观,便于维护,避免全局映射可能带来的类型冲突
  • 性能更优,不需要额外的反射匹配开销

内容的提问来源于stack exchange,提问作者kzfid

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:45:13