1. 为什么选择Dapper?
在.NET生态中,ORM框架的选择往往让人纠结。Entity Framework功能强大但略显笨重,纯ADO.NET又需要重复造轮子。我五年前接手一个日均百万级查询的电商系统时,Entity Framework在高峰期频繁出现性能瓶颈,直到把核心模块改用Dapper才解决问题。
Dapper最吸引我的是它保持原生SQL的精准控制,同时提供对象映射的便利性。比如处理分页查询时,EF Core生成的SQL可能包含不必要的JOIN,而Dapper允许你直接优化关键语句:
csharp复制var sql = @"SELECT * FROM Products
WHERE CategoryId = @categoryId
ORDER BY Price DESC
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY";
var products = connection.Query<Product>(sql, new {
categoryId = 5,
skip = 20,
take = 10
});
这种轻量级特性使它在以下场景特别突出:
- 高并发API服务:某金融项目实测Dapper的吞吐量比EF高37%
- 遗留系统改造:不需要迁移整个数据访问层
- 复杂报表查询:直接编写优化过的SQL语句
- 微服务架构:每个服务独立选择最适合的ORM
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 性能对决:Dapper vs 重型ORM
去年我做过的基准测试显示,在相同硬件环境下查询10000条记录:
- Dapper平均耗时82ms
- EF Core原始SQL查询耗时121ms
- EF Core LINQ查询耗时193ms
差异主要来自三个方面:
- 映射机制:Dapper使用动态代码生成(Emit),而EF依赖表达式树解析
- 查询构造:Dapper不额外处理SQL语句
- 缓存策略:EF的变更跟踪需要额外内存
但要注意,Dapper的性能优势主要体现在读操作。对于需要级联更新的事务场景,EF的ChangeTracker反而更高效。我的经验法则是:
- 读多写少 → Dapper
- 复杂事务 → EF
- 混合场景 → 两者结合使用
3. 多数据库支持实战
Dapper通过ADO.NET基础提供程序支持几乎所有主流数据库。我在最近的项目中同时操作SQL Server和MySQL时,只需要切换连接对象:
csharp复制// SQL Server
using var sqlConn = new SqlConnection(connStr);
var users = sqlConn.Query<User>("SELECT * FROM Users");
// MySQL
using var mysqlConn = new MySqlConnection(mysqlConnStr);
var products = mysqlConn.Query<Product>("SELECT * FROM Products");
跨数据库分页是个典型痛点。不同数据库的语法差异可以通过Dapper的灵活性解决:
csharp复制string GetPagedQuery(string dbType) => dbType switch {
"sqlserver" => "OFFSET @Offset ROWS FETCH NEXT @Limit ROWS ONLY",
"mysql" => "LIMIT @Limit OFFSET @Offset",
"postgresql" => "LIMIT @Limit OFFSET @Offset",
_ => throw new NotSupportedException()
};
4. 高级映射技巧
处理一对多关系时,Dapper的多重映射功能比想象中强大。比如查询博客及其所有评论:
csharp复制var sql = @"SELECT b.*, c.*
FROM Blogs b
LEFT JOIN Comments c ON b.Id = c.BlogId
WHERE b.Id = @id";
var blogDict = new Dictionary<int, Blog>();
var result = connection.Query<Blog, Comment, Blog>(
sql,
(blog, comment) => {
if (!blogDict.TryGetValue(blog.Id, out var existingBlog)) {
existingBlog = blog;
existingBlog.Comments = new List<Comment>();
blogDict.Add(blog.Id, existingBlog);
}
if (comment != null) existingBlog.Comments.Add(comment);
return existingBlog;
},
new { id = 1 },
splitOn: "Id"
);
对于存储过程调用,Dapper比EF更直观:
csharp复制var parameters = new DynamicParameters();
parameters.Add("@userId", 123);
parameters.Add("@result", dbType: DbType.Int32, direction: ParameterDirection.Output);
connection.Execute("sp_GetUserStats", parameters, commandType: CommandType.StoredProcedure);
var result = parameters.Get<int>("@result");
5. 事务与批量操作
Dapper处理事务有两种推荐方式。简单事务:
csharp复制using var trans = connection.BeginTransaction();
try {
connection.Execute("UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1", transaction: trans);
connection.Execute("UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2", transaction: trans);
trans.Commit();
} catch {
trans.Rollback();
throw;
}
批量插入使用表值参数能大幅提升性能:
csharp复制var dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Age");
foreach(var user in users) {
dt.Rows.Add(user.Name, user.Age);
}
var param = new SqlParameter("@tvp", dt) {
TypeName = "dbo.UserTableType",
SqlDbType = SqlDbType.Structured
};
connection.Execute("sp_InsertUsers", param, commandType: CommandType.StoredProcedure);
6. 性能调优经验
经过多个项目实践,我总结出这些Dapper优化技巧:
- 连接管理:始终使用using语句,避免连接泄露
- 参数化查询:永远不要拼接SQL字符串
- 异步操作:合理使用QueryAsync等异步方法
- 缓存策略:对静态数据使用MemoryCache
- 批处理:将多个小查询合并为一个大查询
一个常见的反模式是N+1查询问题。优化前:
csharp复制var users = connection.Query<User>("SELECT * FROM Users");
foreach(var user in users) {
user.Orders = connection.Query<Order>("SELECT * FROM Orders WHERE UserId = @id", new { id = user.Id });
}
优化后使用IN查询:
csharp复制var users = connection.Query<User>("SELECT * FROM Users").ToList();
var userIds = users.Select(u => u.Id);
var orders = connection.Query<Order>("SELECT * FROM Orders WHERE UserId IN @ids", new { ids = userIds })
.ToLookup(o => o.UserId);
foreach(var user in users) {
user.Orders = orders[user.Id].ToList();
}
7. 与Entity Framework混合使用
很多项目并非非此即彼。我在当前项目中这样组合使用:
- Dapper处理报表查询和批量操作
- EF Core处理复杂领域模型和变更跟踪
通过共享DbContext的连接对象实现无缝集成:
csharp复制// 在EF Core中获取Dapper连接
var conn = context.Database.GetDbConnection();
var rawData = conn.Query<ReportItem>("...");
这种混合模式需要特别注意:
- 事务边界要明确
- 避免在同一个事务中混用不同ORM的变更操作
- 连接字符串配置保持一致
8. 监控与调试
Dapper的简洁性也意味着需要更多手动监控。我推荐这些工具:
- MiniProfiler:可视化SQL执行时间
- Dapper.Logging:集成到现有日志系统
- 自定义拦截器:记录慢查询
安装MiniProfiler只需几行代码:
csharp复制services.AddMiniProfiler(options => {
options.RouteBasePath = "/profiler";
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
}).AddEntityFramework();
在开发环境配置连接字符串时加上这些参数可以获取更多信息:
code复制Application Name=MyApp;Workstation ID=MyDevPC;...
最近处理过一个性能问题:某查询在测试环境很快但生产环境很慢。最终发现是Dapper默认不启用MARS(Multiple Active Result Sets),在需要同时处理多个结果集时需要显式开启:
code复制Server=...;MultipleActiveResultSets=True;
