
每隔一阵,Dapper 都会被重新问一次:现在都已经 Entity Framework Core 这么成熟了,为什么还有人坚持手写 SQL、坚持用 micro-ORM?
这篇讲 Dapper in Depth with ASP.NET Core 10 的文章,其实给了一个挺典型的答案:因为有一类系统,真的更在乎可控、直白和性能,而不是 ORM 帮你包办一切。
它不是那种特别有新理论的文章,但对很多还在纠结“EF Core 还是 Dapper”的人来说,价值正好在于它把 Dapper 的使用场景、常见能力和工程结构重新摆了一遍。你看完不会突然学会什么魔法,但会更清楚:Dapper 到底强在哪,麻烦又在哪。
Dapper 的核心吸引力,始终没变
文章一开始对 Dapper 的定义很传统:它是 Stack Overflow 团队做的 micro-ORM,建立在 ADO.NET 之上,抽象很薄,速度很快,直接把 SQL 结果映射到强类型对象。
这些话大家都听过,但真正重要的是,它背后的 trade-off 也一直没变:
- 你获得了更高的执行可控性
- 你保留了写 SQL 的自由
- 你减少了 ORM 的额外开销
- 但你也得自己承担更多查询设计和维护责任
这其实就是 Dapper 的全部魅力和全部代价。
它吸引人的地方,不是“它比 EF Core 高级”,而是它更诚实。你写什么 SQL,它就跑什么 SQL。不会替你做很多聪明事,也不会偷偷帮你把复杂度藏起来。对于想精确控制数据访问层的人,这种直白反而是优点。
它最适合的,不是所有系统
原文列了一些典型场景:高流量 API、微服务、金融系统、报表平台、读多写少的系统。
这个判断基本靠谱。
因为 Dapper 真正舒服的地方,往往出现在下面这类情况:
- 查询路径很明确
- SQL 本身是业务核心的一部分
- 性能要求比较敏感
- 你不想把查询优化交给 ORM 猜
- 你愿意自己维护数据库访问代码
比如报表接口、分析接口、复杂筛选列表、聚合查询、一些典型的后台服务,这些地方 Dapper 常常很顺手。你写一条清晰的 SQL,参数化执行,映射回 DTO 或模型,事情就结束了。
但如果你的系统更依赖这些能力:
- 实体跟踪
- 自动变更检测
- 数据迁移工作流
- 更完整的导航属性和关系管理
- 更高层的统一抽象
那 Dapper 就不会那么“轻松”,而会开始显得手工活偏多。
所以更准确的说法不是“Dapper 更好”,而是:Dapper 对某些后端场景更顺手。
文章最实用的部分,是把常用方法和高级能力串起来了
这篇文章对 Dapper 的方法体系梳理得比较全。从 Query<T>()、QueryAsync<T>()、Execute() 一直到 QueryMultiple()、multi-mapping、transactions、stored procedures、bulk operations`,基本把大家平时真会碰到的东西都扫了一遍。
如果你已经用过 Dapper,这部分不算新鲜;但如果你只停留在“我知道 Dapper 能查表”的阶段,它至少能帮你建立一个更完整的认知:Dapper 不只是查一行、改一行,它也能承接一些复杂一点的数据库交互。
尤其下面几块比较值得记:
QueryMultiple 很适合聚合接口
原文举了一个典型例子:一次查询里同时拿 product、reviews、category。
这类能力在 API 层其实挺常见。与其跑三次往返,不如一口气查出多个结果集,再逐个读取。对于 dashboard、详情页、聚合接口,这类写法很实用。
Multi-mapping 解决 JOIN 后的对象映射
这块也很典型。很多人一开始用 Dapper 会觉得 JOIN 之后映射回对象不够优雅,但它其实提供了多对象映射能力,只是比完整 ORM 更显式、更手工一点。
换句话说,Dapper 不是做不到关系映射,只是它要求你自己更清楚地写出“怎么拼”。
Transaction 依然是 ADO.NET 那套现实
这一点反而挺好:Dapper 没有发明另一套 transaction 哲学,它基本还是沿用 IDbTransaction。这意味着你掌控感更强,但也意味着你不能偷懒。
这很适合订单、库存、支付之类强一致性场景,但前提是你团队得真懂事务,不是把 BeginTransaction() 当护身符念咒。
文章里的“仓储模式 + 服务层”写法,能用,但别神化
原文也给了一个比较标准的 ASP.NET Core Web API 分层:
- Controller
- DTO
- Service Layer
- Repository + Dapper
- SQL Server
这套结构当然能用,而且对很多中小型项目来说很熟悉、很稳。
但说实话,Dapper 和 repository pattern 的关系没有很多文章写得那么天作之合。
因为 Dapper 的一个特点就是:SQL 本身常常就是业务行为的一部分。如果你把 repository 抽得太厚,最后很容易写出一堆只是帮你转发 SQL 的接口,既没减少复杂度,也没增加多少可维护性。
所以我觉得更实在的态度是:
- 小项目:仓储层可以有,但别过度包装
- 查询复杂的系统:更应该关心 query object / feature-based slice / read model 这些组织方式
- 写操作复杂的系统:要把事务边界和业务行为设计清楚,而不是只靠“有 service 层”自我安慰
换句话说,Dapper 最怕的不是 SQL 多,而是结构装得太优雅,结果每层都只是在传话。
它对比 EF Core 的意义,不是赢,而是明确边界
原文多次暗示 Dapper 和 EF Core 的差异,这其实是多数读者最关心的部分。
我觉得最该抓住的不是“谁更好”,而是这一句:
Dapper 适合你想自己掌控 SQL 的时候;EF Core 适合你愿意用更高抽象换开发便利的时候。
很多团队会在这两者之间摇摆,核心原因不是技术优劣,而是团队的真实能力和项目的真实约束不同。
如果团队里:
- 有人擅长 SQL
- 愿意持续审查询句
- 能把数据库访问当一等公民维护
那 Dapper 会越用越顺。
但如果团队更依赖:
- 快速 CRUD 开发
- 高统一性的数据访问方式
- 用框架默认能力降低决策成本
那 EF Core 更容易把整体协作成本压下来。
所以这不是“轻量击败重量”的故事,而是:你到底想把复杂度放在哪一层。
真正需要警惕的是“手写 SQL 的自信膨胀”
文章里也提到参数化查询、防 SQL 注入、异步方法、事务这些好习惯,这些当然都对。
但 Dapper 真正危险的地方,不是大家不知道这些概念,而是很容易在“我自己写 SQL,所以我更懂数据库”这件事上过度自信。
现实里,很多 Dapper 项目后面会遇到的问题其实是:
- SQL 分散在各处
- 查询命名混乱
- 映射模型越来越多
- JOIN 和查询条件逻辑复制粘贴
- 事务边界和业务边界开始打架
也就是说,Dapper 不是更低复杂度,而是把复杂度从 ORM 机制里拿出来,摆到你自己面前。
如果团队不打算认真管这件事,那“手写 SQL 的掌控感”最后可能会变成“到处都是没人敢动的查询字符串”。
这篇文章最值得带走的判断
如果把这篇长文压成一句最实用的话,我觉得大概是:
Dapper 不是用来替代所有 ORM 的,它是给那些明确知道自己为什么要写 SQL 的团队准备的。
这句话很重要。
因为很多工具争论最后都会失焦,变成“哪个更现代”“哪个性能更高”“哪个公司更常用”。但 Dapper 的问题从来不在这些标签,而在于:你的系统是不是需要这种薄抽象,你的团队是不是能驾驭它带来的裸露复杂度。
如果答案是“是”,那 Dapper 在 ASP.NET Core 10 这种高性能 Web API 场景里,依然非常能打。
如果答案是“不是”,那也没必要为了追求“轻量”而硬上。
参考
- Dapper in Depth with ASP.NET Core 10 — C# Corner
- Dapper on NuGet — NuGet
- ADO.NET overview — Microsoft Learn