Dapper多表JOIN需手动映射:用Query配合splitOn指定第二实体起始字段别名;一对多用QueryMultiple分步查再内存关联;字段不匹配优先用SQL别名或Column特性解决。
多表 JOIN 查询后怎么映射到多个实体?
dapper 本身不支持自动多实体映射(比如 select * from order o join customer c on o.customerid = c.id 返回混合字段后,直接映射成 order 和 customer 两个对象),必须手动拆解。常见错误是直接用 query<order customer order></order> 却没传对参数顺序或没写 spliton。
正确做法是显式指定哪个字段作为第二实体的起点:
var sql = @"SELECT o.Id, o.Total, c.Id AS CustomerId, c.Name
FROM Orders o
JOIN Customers c ON o.CustomerId = c.Id";
var result = connection.Query<Order, Customer, Order>(
sql,
(order, customer) => { order.Customer = customer; return order; },
splitOn: "CustomerId");-
splitOn值必须是 SQL 中第二个实体第一个字段的别名(这里是c.Id AS CustomerId,所以填"CustomerId") - 如果 JOIN 后有多个实体(如加个
Address),要链式调用Query<a></a>并依次指定多个splitOn字段,但字段名不能重复 - 字段别名必须唯一且明确——
c.Id别写成Id,否则和o.Id冲突,Dapper 会从第一个Id就切分,导致映射错乱
一对多关系(比如订单+订单项)怎么一次性查出来?
Dapper 没有内置的“一对多懒加载”或嵌套集合填充,靠 QueryMultiple 手动组装最稳。别试图用单条 SQL + 多实体映射去拼集合——它只会把每个订单项都生成一个新 Order 实例,造成大量重复对象。
典型做法是用存储过程或两个结果集:
var sql = @"
SELECT * FROM Orders WHERE Id IN @ids;
SELECT * FROM OrderItems WHERE OrderId IN @ids;";
using var multi = connection.QueryMultiple(sql, new { ids = orderIds });
var orders = multi.Read<Order>().ToList();
var items = multi.Read<OrderItem>().ToList();
<p>// 手动关联
foreach (var order in orders)
{
order.Items = items.Where(i => i.OrderId == order.Id).ToList();
}-
QueryMultiple要求 SQL 用分号分隔多个查询,且数据库驱动必须支持多结果集(SQL Server、PostgreSQL 可以,SQLite 默认不行) - 别在循环里查数据库——用
IN一次拉回所有依赖数据,再内存关联,性能差一个数量级 - 注意
Read<t>()</t>是顺序读取,不能跳过某个结果集,也不能反复读同一个
使用 Query 映射时字段名对不上怎么办?
常见现象:SQL 里写了 SELECT o.Name AS CustomerName,但实体属性叫 CustomerFullName,Dapper 默认按名称匹配失败,返回 null 或默认值。
有三个解法,按优先级推荐:
- 加列别名,让 SQL 字段名和属性名完全一致:
SELECT o.Name AS CustomerFullName - 用
[Column("Name")]特性标记属性(需引用System.ComponentModel.DataAnnotations.Schema) - 全局配置
SqlMapper.AddTypeHandler<string>(new YourCustomHandler())</string>——太重,只在特殊类型转换时用
注意:Dapper 匹配不区分大小写,但空格、下划线、Pascal 风格必须一致;CustomerName 不会匹配 customer_name,除非你自定义了驼峰转换逻辑。
复杂查询要不要封装成视图或存储过程?
当 JOIN 表超过 3 个、WHERE 条件动态拼接多于 4 种组合、或需要窗口函数/CTE 时,硬写在 C# 字符串里极易出错且无法复用。不是“能不能”,而是“值不值得维护”。
实际建议:
- 简单两表关联、固定条件 → 直接写 SQL 字符串,清晰可控
- 涉及分页 + 总数计算(
COUNT(*) OVER())、递归树、行号排序 → 上存储过程,避免在 C# 里拼ROW_NUMBER() OVER(ORDER BY ...)这种易错结构 - 团队共用的统计口径(如“活跃用户定义”)→ 提炼成数据库视图,C# 层只查视图,语义明确,权限好管
别为了“ORM 纯洁性”死扛复杂 SQL。Dapper 的优势本就是让你写 SQL,而不是绕着它走。真正容易被忽略的是:存储过程中字段变更后,C# 实体没同步更新,运行时报 Invalid cast from 'Int32' to 'String' 这类错,得翻 SQL 和实体两边才能定位。


















