使用Dapper映射子对象:如何返回包含全部订单项的订单?
解决Dapper中订单与多订单项的映射问题
嘿,我来帮你搞定这个Dapper的一对多映射问题!你遇到的核心问题是:你的SQL查询返回的是扁平化的结果集——同一个订单的每个订单项对应一行记录,订单信息会重复出现。而你用FirstOrDefault()只取了第一行,自然只能拿到第一个订单项。
Dapper本身支持一对多的结果映射,我们只需要用正确的方式把这些重复的订单记录合并,把所有订单项归集到对应的订单对象里就行。下面给你两种常用的解决方案:
方案一:使用Dapper多映射+字典分组(最常用)
这种方法利用Dapper的Query<TFirst, TSecond, TResult>重载,配合字典来避免重复创建订单对象,同时把订单项逐个添加到订单的列表中。
步骤1:确保实体类正确初始化列表
首先,你的Order类要提前初始化LineItems列表,避免后续添加订单项时出现空引用异常:
public class Order { public int OrderId { get; set; } public DateTime OrderDate { get; set; } public string CustomerName { get; set; } // 初始化列表,避免NullReferenceException public List<LineItem> LineItems { get; set; } = new List<LineItem>(); } public class LineItem { public string SKU { get; set; } public int Quantity { get; set; } }
步骤2:编写查询并处理映射
// 关联订单和订单项的SQL查询,确保订单字段在前,订单项字段在后 var sql = @" SELECT o.OrderId, o.OrderDate, o.CustomerName, li.SKU, li.Quantity FROM Orders o INNER JOIN LineItems li ON o.OrderId = li.OrderId WHERE o.OrderId = @OrderId"; // 用字典来跟踪已经创建的订单对象,避免重复 var orderLookup = new Dictionary<int, Order>(); // 调用Dapper的多映射Query方法 var results = connection.Query<Order, LineItem, Order>( sql, (order, lineItem) => { // 检查字典里是否已经有这个订单 if (!orderLookup.TryGetValue(order.OrderId, out var existingOrder)) { existingOrder = order; orderLookup.Add(existingOrder.OrderId, existingOrder); } // 把当前订单项添加到订单的列表中 existingOrder.LineItems.Add(lineItem); return existingOrder; }, // 查询参数 param: new { OrderId = targetOrderId }, // 关键:指定拆分列——结果集中第一个属于LineItem的列名 splitOn: "SKU" ); // 最终拿到合并后的订单(去重后取第一个) var finalOrder = results.Distinct().FirstOrDefault();
这里的splitOn参数非常关键:它告诉Dapper,从结果集的哪一列开始,把数据映射到第二个实体(LineItem)上。因为你的查询结果里,SKU是第一个属于订单项的字段,所以填"SKU"。
方案二:手动处理结果集(适合简单场景)
如果你的查询结果已经拿到手(比如你之前的result变量包含多条记录),也可以手动遍历结果来合并:
// 假设你已经通过Query拿到了所有扁平化的记录,比如用匿名类型或者Tuple var flatResults = connection.Query(sql, param: new { OrderId = targetOrderId }); // 先创建订单对象,取第一条记录的订单信息 var order = new Order { OrderId = flatResults.First().OrderId, OrderDate = flatResults.First().OrderDate, CustomerName = flatResults.First().CustomerName, LineItems = new List<LineItem>() }; // 遍历所有记录,把订单项添加进去 foreach (var item in flatResults) { order.LineItems.Add(new LineItem { SKU = item.SKU, Quantity = item.Quantity }); }
这种方法更直观,但代码量稍多,适合快速调试或者简单的场景。
关键注意事项
- 确保你的SQL查询返回的字段顺序:订单的所有字段在前,订单项的字段在后,这样Dapper才能正确拆分映射。
splitOn的列名要和查询结果中的列名完全匹配(注意数据库是否区分大小写)。- 一定要初始化
Order类的LineItems列表,否则添加订单项时会抛出NullReferenceException。
内容的提问来源于stack exchange,提问作者Jason Jay
相关产品推荐
相关产品推荐

