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
相关产品推荐
相关产品推荐

