☰
.NET主流ORM功能详解:EF Core、Dapper、SqlSugar选型与性能对比
2026/10/14 23:41:47 网站建设 项目流程

做.NET开发这几年,几乎每个项目开场都是同一个问题:这次用哪个ORM?这个问题能吵一整天。EF Core、Dapper、SqlSugar、NHibernate、RepoDb……每一个都有人站队,每一个都有人踩坑。写这篇.NET主流ORM功能介绍大全,不是想告诉你“某某天下第一”,而是把我实际用下来的功能拆解、性能数据和迁移教训整理出来。看这篇东西的人,可能是刚入行的开发者,也可能是被存量项目逼着迁移的老手。不管你属于哪一类,读完至少能对“哪个ORM解决什么问题”有清晰判断,不至于被PPT式的性能对比带偏。

1. 先说清楚:.NET生态为什么需要这么多种ORM

1.1 从ADO.NET到全功能ORM:一个演化故事

很多人刚接触.NET时,第一课是ADO.NET,写SqlConnection、SqlCommand、DataReader,然后手动把数据塞进实体类。那会儿感觉还行,但项目一复杂就崩溃:几十张表、几百个字段,光是写映射代码就能写到怀疑人生。

于是ORM出现了。它的核心价值其实就一句话:让开发者用面向对象的方式操作数据库,把“关系模型”和“对象模型”之间的转换交给框架去干。按这个标准,.NET生态里的ORM可以分成两个阵营:

  • 全功能ORM:EF Core、NHibernate、SqlSugar,自带实体映射、查询构建、状态跟踪、迁移工具,适合业务逻辑密集的CRUD系统。
  • 微ORM:Dapper、RepoDb,只做一件事——把SQL查询结果快速映射成对象,其余全部交给你手写。

这就像一个是全自动挡汽车,一个是手动挡性能车。自动挡省心但操控感弱,手动挡累但提速直接。没有谁取代谁,只有你用在哪条路上。这也是为什么“选型之争”永远存在——因为大家的业务场景根本不一样。

1.2 选型先想清楚三个问题

踩过几次坑之后,我现在选ORM只问三个问题:

  1. 业务核心是CRUD还是复杂查询?如果90%的代码都是增删改查,全功能ORM能省一半代码;如果是报表、财务对账这种复杂SQL,手写反而更靠谱。
  2. 用的什么数据库?有些ORM对国产库(达梦、人大金仓、GaussDB)支持不到位,这在实际项目里是致命的。
  3. 团队水平怎么样?EF Core的延迟加载、状态跟踪,用好了是效率神器,用不好就是线上事故,Dapper直白,新手不容易写出“玄学代码”但更累。

这三个问题没有标准答案,但能在十分钟内筛掉一半候选方案。

1.3 一张表看清六大ORM的定位

我整理了一份速查表,基本代表目前的主流选择:

ORM类型关键优势主要短板最适配场景
EF Core全功能微软官方、功能最全、迁移好用复杂查询性能弱、状态机坑多CRUD业务系统、原型到落地
Dapper微ORM性能天花板、SQL自由度最高无状态跟踪、CRUD全靠手写模板高并发读写、复杂报表
SqlSugar全功能国产库适配好、API直观、更新快国际社区小、生态依赖国内信创项目、中小团队快速交付
NHibernate全功能老牌稳定、缓存机制成熟学习曲线陡、配置繁琐存量金融/政务项目维护
RepoDb微ORM动态合并、批量操作性能好社区小、资料少批量导入导出场景
LinqToDB中间型LINQ体验好、支持原生SQL混写名气不大、坑得自己踩需要LINQ但受不了EF重的团队

这张表没有对错,只有合不合适。接下来我把每个框架的核心功能和真实感受展开讲。

2. 逐个拆解:主流ORM的核心功能与实战表现

2.1 EF Core:功能最全,但不等于“最好用”

EF Core是微软亲儿子,跟着.NET版本迭代,功能覆盖面是六款里最全的。核心功能包括:LINQ查询、变更跟踪、迁移、全局过滤、延迟加载/显式加载、乐观并发控制,还有8.0引入的Complex Types和JSON列映射。

我最常用的其实是几个“生态级”能力:

  • 迁移工具:dotnet ef migrations add一条命令生成SQL脚本,配合CI/CD自动执行,环境差异问题大幅减少。
  • 全局过滤:软删场景下,HasQueryFilter一条配置,所有查询自动拼接IsDeleted = 0,不用在每个查询里重复写。
  • LINQ的强类型体验:写代码时编译器就帮你查字段拼写错误,重构时也安全。

但千万别迷信“EF Core = 省心”。变更跟踪(ChangeTracker)是双刃剑:一个实体被查询出来后,只要DbContext还活着,任何属性修改都会被跟踪,最后SaveChanges时全量更新。如果你在一个长生命周期的作用域里查询了一堆实体,然后只改其中一个,EF Core会生成一条包含所有字段的UPDATE语句——字段多的时候性能直接崩。

实际项目中,我遇到过一次极端案例:某报表页面调用了一个复杂查询,返回几百个实体,然后循环里改了每个实体的一个状态字段,再SaveChanges。生成的SQL巨大,数据库CPU直接打满。排查半天才发现是“全字段更新”导致的。解决办法很粗暴:查询时加.AsNoTracking(),改完后单独创建更新实体。

提示:EF Core 8.0/9.0对JSON列的支持越来越强,但在复杂JSON查询上还远不如用原生数据库函数。

2.2 Dapper:SQL在手,天下我有

Dapper是Stack Overflow团队开源出来的,定位极其纯粹——一个高效的对象映射器,不碰任何查询构建。它最大的优势就是“透明”:你写什么SQL,就执行什么SQL,没有中间商赚差价,性能自然拉满。

核心API就几个:

  • Query<T>:查询并映射为强类型对象
  • Execute:执行增删改
  • QueryFirst/QuerySingle:取单条
  • QueryMultiple:一次查询返回多个结果集
  • ExecuteScalar:取标量值

举个典型例子:

var sql = @" SELECT o.Id, o.OrderNo, c.Name AS CustomerName FROM Orders o INNER JOIN Customers c ON o.CustomerId = c.Id WHERE o.CreateTime >= @start AND o.Status = @status ORDER BY o.CreateTime DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY"; var list = connection.Query<OrderVm>(sql, new { start = DateTime.Now.AddDays(-7), status = 1, offset = 0, pageSize = 20 }).ToList();

看到了吗?SQL是裸的,参数是匿名对象,结果直接映射为OrderVm。没有表达式树、没有状态跟踪、没有奇怪的SQL生成,这在复杂查询场景下就是性能保障。

但Dapper的短板也很明显:CRUD要自己写。我都习惯建一个“仓储基类”把通用增删改查封装起来,不然每个实体手写一遍INSERT/UPDATE,心情极度暴躁。

有一个坑必须提醒:Dapper的查询结果是“瞬时的”,实体和数据库之间没有任何关联。更新时必须把实体重新传给UPDATE语句,或者用一个包含主键和待更新字段的专用实体。很多人从EF转过来,会下意识改Dapper查出来的对象然后期待它自动同步——然后发现什么都没发生。

2.3 SqlSugar:国产ORM里的“全能选手”

SqlSugar在国内开发者里口碑一直不错,尤其这几年信创趋势下,它对国产数据库的适配是一大亮点。内置功能极其丰富:CodeFirst、DbFirst、分表分库、多租户、逻辑删除、乐观锁、批量操作、读写分离,基本你能想到的它都有。

它的API风格非常“中国式直觉”,上手速度快。比如分页查询:

var pageResult = db.Queryable<Order>() .Where(o => o.Status == StatusEnum.Paid) .Where(o => o.CreateTime >= startTime) .OrderBy(o => o.Id, OrderByType.Desc) .ToPageList(pageIndex, pageSize);

这段代码用起来非常直观,语义清晰,不需要理解EF Core那套Skip/Take和表达式树里的坑。尤其是批量操作,SqlSugar的Storageable和BulkCopy性能出色,插入几万条数据时比EF Core快十倍以上。

它的核心优势还在于“省心”:

  • 自动处理数据库差异:同样的代码,切到MySQL、SQLServer、达梦、人大金仓,SQL方言由框架翻译,项目迁移数据库时能少改很多代码。
  • 内置逻辑删除:实体上有标记就自动过滤,不用像EF那样手动配置全局过滤器。
  • 多表查询:内置Join方法,比手写SQL动态拼接简单不少。

我一般在快速交付的项目、信创项目、中小团队协作场景首选SqlSugar。但也要说实话:它在国际社区的知名度不高,英文文档很少,遇到冷门bug时搜不到现成解决方案的概率比EF Core高。

注意:SqlSugar的分表分库功能,在真实项目里建议只在确定的场景使用(比如按订单号分表),全局启用会遇到跨分片查询、事务一致性等一系列复杂度。

2.4 NHibernate:老而弥坚,但学习曲线陡峭

NHibernate是Java Hibernate的.NET移植版,出道早,巅峰期在.NET Framework时代,现在用的人少了,但存量项目不少,尤其是一些金融、政务系统的核心模块还跑在它上面。

它的强项是成熟的二级缓存、持久化生命周期管理和复杂的继承映射,概念非常多:Session、SessionFactory、持久态、游离态……我当年第一次接触时直接懵了,感觉不是在写ORM,是在学习一套完整的状态机理论。

说实话,新项目我基本不推荐NHibernate。理由很简单:团队招人难,资料老,坑多。但如果你要维护老系统,必须掌握几个关键点:

  • 查询用ISession.Query<T>()走LINQ,少用HQL,代码可读性好一些。
  • 二级缓存配置要在多个节点环境下特别小心,一旦缓存与数据库不一致,出现脏读的排查难度极大。
  • NHibernate的“延迟加载(Lazy Loading)”比EF更激进,Session关闭后再访问导航属性,会直接抛异常,而且错误信息还经常不直观。

2.5 RepoDb与LinqToDB:小众但不能忽视

RepoDb是混合型ORM的典型代表,核心卖点是动态合并和批量操作性能出色。它的Merge<T>()方法能根据主键自动判断插入还是更新,在同步数据场景超级好用;批量删除和批量插入也做了深度优化。

LinqToDB则介于EF和Dapper之间:有LINQ查询体验,又不会像EF那样生成失控的SQL,支持SQL混写。可能更适合“想用LINQ但不想碰EF状态机”的团队。

这两个框架的社区规模都比较小,遇到问题时能查到的资料有限。我的建议是:新技术用它们没问题,但团队里至少要有人能吃透源码,不然生产环境出了奇怪的问题会很被动。

2.6 各ORM在典型业务场景中的适配度

从真实业务数据维度来看:

  • 高并发读场景(比如商品详情页):Dapper > RepoDb > LinqToDB > SqlSugar > EF Core。
  • 复杂CRUD管理后台:SqlSugar ≈ EF Core > NHibernate > Dapper(这里Dapper垫底是因为写CRUD太累)。
  • 报表/财务对账(大量复杂SQL):Dapper > LinqToDB > 其他,手写SQL的掌控力是无价的。
  • 国产数据库强制要求:SqlSugar > EF Core(部分版本需要第三方驱动)> 其他。
  • 老系统维护:NHibernate(如果项目本来就是)或Dapper重写(如果是烂到没法看)。

3. 性能真相:为什么跑分差距那么大

3.1 用BenchmarkDotNet实测的一组数据

我经常被问“到底哪个ORM快”。这个问题得分场景。下面是我在一台普通开发机上、数据量50万条环境下跑过的一组基准测试结果(相对耗时,非绝对耗时):

操作类型EF CoreDapperSqlSugarRepoDb
主键单条查询基准(最慢)快约2.5倍快约2倍快约3倍
六表Join聚合查询基准快约4倍快约2.5倍快约5倍
批量插入1万条基准快约8倍(配合SqlBulkCopy)快约6倍(BulkCopy内置)快约9倍
复杂条件分页基准快约3倍快约2倍快约3倍

这个结果很多人看到后第一反应是“EF Core太垃圾了”,但我想说的是:跑分快慢,不代表你的业务价值。Dapper快是因为它几乎不做额外工作,把SQL直接给ADO.NET执行,然后映射一下就完事;EF Core慢是它要在背后帮你做表达式树翻译、状态跟踪、缓存的差异比对。

3.2 慢的根源到底在哪

EF Core慢的核心原因有三个:

  1. 表达式树翻译开销:C#的LINQ表达式要翻译成SQL,这是CPU密集工作,而且翻译质量不稳定——同一个查询换个写法,SQL天差地别。
  2. 状态跟踪开销:默认查询都会进入ChangeTracker,EF要维护每个实体的快照,内存和CPU成本都不低。
  3. SQL生成质量:复杂查询下EF生成的SQL常有冗余子查询、不合理的JOIN顺序,数据库优化器再神也救不回来。

Dapper几乎没有这些成本:SQL是自己写的,参数是直接传给ADO.NET的,映射走的是IL动态生成,代价只在第一次调用时。所以Dapper快不是在“优化”上下功夫,而是“什么都不做”。

3.3 不同场景下的选型建议

基于以上数据,我个人比较“功利”的选型原则如下:

  • 读多写少、高吞吐:Dapper是底线选项。这不是说比谁快,而是能让你看清每一条SQL,避免“慢查询”出现在你不注意的角落里。
  • 管理后台、内部系统、CRUD密集:EF Core或SqlSugar。开发效率的提升远远覆盖那点查询性能损失。
  • 报表系统:Dapper + 手写优化SQL。给数据库加索引、调整执行计划,这些在EF Core里非常难做到。
  • 混合数据规模:读用Dapper,写用全功能ORM。这也不是什么高深做法,商业项目里非常常见。

很多团队盲目追求“极致性能”,结果业务还没跑起来就被Dapper的CRUD代码拖垮了开发效率;反过来,有些团队闭眼EF Core一把梭,线上一个复杂查询就超时。性能是衡量的维度之一,但不是唯一维度。你自己项目的复杂度、团队效率和可维护性,远比单次查询快几毫秒重要。

4. 落地实操:混合使用与迁移的完整记录

4.1 混合架构:读写分离的具体做法

先分享一个我在某电商库存项目里的真实方案。这个项目查询量巨大,但写操作相对简单,于是我们采用了“Dapper + EF Core”混合架构:

  • 查询走Dapper:所有列表页、详情页、统计接口,用Dapper手写SQL,以最优执行计划为准。
  • 写操作走EF Core:订单创建、状态流转等写操作,利用EF Core的乐观并发和事务能力,避免手写一堆冲突判断。
  • 两者共用同一个连接字符串和事务:创建IDbConnection时同时拿到DbContext,用Database.UseTransaction把两者置于同一个事务中。

关键代码大致是这样:

using var connection = new SqlConnection(connectionString); connection.Open(); using var transaction = connection.BeginTransaction(); // 写操作:EF Core var order = new Order { UserId = userId, Total = total, Status = OrderStatus.Created }; await dbContext.Orders.AddAsync(order); await dbContext.Database.UseTransaction(transaction); await dbContext.SaveChangesAsync(); // 读操作:Dapper,同一个事务 var detail = connection.QueryFirstOrDefault<OrderDetail>( "SELECT * FROM Orders WHERE Id = @id", new { id = order.Id }, transaction);

这种做法的最大好处是:每个数据访问都选择了最合适的工具,而不是让一个工具干所有事。坏处也明显:团队要做好约定,否则很容易出现“这个查询为什么不用Dapper写”“这个更新为什么不用EF”的争论。

4.2 从EF Core迁移到Dapper的四个关键步骤

如果你的项目决定从EF Core迁移到Dapper,别指望一夜完成,下面是四个实操步骤,踩过的坑都写在里面:

  1. 先审计所有EF查询,划分优先级。把复杂查询、高并发查询列为第一批迁移对象,简单的CRUD可以继续用EF,不必追求一步到位。
  2. 建立参数化SQL的规范。EF Core用参数是自动的,Dapper虽然也自动,但要求你写SQL时使用@name占位符。注意不要字符串拼接,防SQL注入不只是安全问题,也是性能问题——拼接SQL会破坏执行计划缓存。
  3. 处理Include调用。EF的.Include()在Dapper里不存在,你需要手动写JOIN或多次查询后在内存中组装实体。推荐后者,复杂场景更可控。
  4. 迁移后必做回归测试。特别注意分页逻辑、软删条件、乐观并发字段的显式处理。

有一个最容易被忽略的地方:EF Core查询默认会自动拼接软软删除过滤条件(如果在模型里配置了全局过滤),但Dapper不会。迁移时手动补上WHERE IsDeleted = 0,漏了就是线上数据泄露事故。

4.3 从SqlSugar迁移到EF Core的注意事项

SqlSugar转到EF Core,方向反过来,也有不少“水土不服”的点:

  • 分页写法差异:SqlSugar的ToPageList内部帮你做了分页数和偏移量的计算,EF里要写Skip/Take,而且Skip/Take必须跟在OrderBy之后,否则某些数据库会报错。
  • 批量更新差异:SqlSugar的UpdateColumns可以指定只更新部分列,EF Core的批量更新在8.0之前都是逐条执行的,8.0引入了ExecuteUpdate才补上这块能力。
  • 实体特性配置:SqlSugar用特性直接标注表名列名,EF Core虽然也支持但推荐在OnModelCreating里用Fluent API配置,信息更集中、便于审查。

我见过一个团队迁移后性能反而变差,排查原因发现是EF默认的查询跟踪导致内存暴涨。迁移EF后第一件事,就是检查所有只读查询是否需要.AsNoTracking()。很多时候不是EF慢,是你在错误地使用它。

5. 常见问题与排查技巧实录

5.1 EF Core的N+1查询与连接池耗尽

N+1是EF Core最经典的坑,没有之一。它长这样:你查了一个订单列表,然后在循环里访问每个订单的客户信息,EF为每个客户信息单独执行了一条查询。列表里有几百条数据,数据库就要执行几百条查询,性能直接垮掉。

排查技巧很简单:在ConfigureLogging里加上EnableSensitiveDataLogging,然后打开控制台输出,所有SQL都会打出来。一旦发现循环查询模式,改用Include或者Select投影。如果数据关联非常复杂,可以拆成多个查询,然后用内存组装。

连接池耗尽的问题通常出现在:长生命周期DbContext + 慢事务。解决办法:

  • 用AddDbContextFactory,每次请求创建新的DbContext实例。
  • 确保所有异步查询都用了await,没被丢弃的Task会占用连接。
  • 事务控制范围要小,不要用事务包住一个需要长耗时计算的循环。

5.2 Dapper的参数化与结果映射

Dapper的自定义映射有一个大坑:默认是按列名匹配属性名,且区分大小写时容易踩坑。如果数据库返回的列名是customer_id,你的实体属性是CustomerId,就直接映射不上——解决办法是SQL里AS CustomerId,或者用Dapper.Contrib的属性配置。

还有一个常见的运行时错误:DynamicParameters使用同名字典时,如果SQL里同一个参数出现两次,DynamicParameters在旧版本可能不生效。我的习惯是:不要依赖同一个DynamicParameters实例在复杂SQL里多次复用,要么写多次参数添加,要么直接改成匿名对象。匿名对象的新鲜度反而更高,因为它每次都是新实例。

在处理结果映射时,建议给每条查询建一个专用的ViewModel或Record,不要直接用实体类。这样既能避免映射到不必要字段,也为SQL的列清单提供了明确约定。用实体类接收未知列,Dapper不会报错,但你会得到一堆null值,而且查不出来为什么。

5.3 SqlSugar的批量更新与自增主键

SqlSugar的Storageable是批量更新的好帮手,它内部会自动判断INSERT还是UPDATE。但有个坑:如果你在循环里反复调用Storageable,性能最好也不如一次性传List。批量操作一定要在内存中先组装好集合,一次调用框架API,数据库往返次数要最小化。

自增主键方面,SqlSugar默认实体主键如果是int类型,插入后会把自增值回填到实体属性上。但如果你用了long类型并且数据库列不是自增而是序列(Oracle、达梦),需要在实体特性或全局配置里声明,否则插入后主键还是0,后续关联逻辑全崩。

注意:SqlSugar的批量插入在数据量超过1万条时,建议直接走BulkCopy接口而不是普通Insertable,前者走的是原生数据库批量协议,后者还是逐条拼接。

5.4 迁移脚本与多ORM混用的事务一致性

项目上线前,最容易被忽略的是“迁移脚本生成”和“事务一致性”两个环节。

迁移脚本我在EF Core里遇到过几次“生成的迁移与实际数据库不一致”,原因大多是开发机和生产环境装了不同版本的数据库引擎。在提交迁移前,务必在目标数据库版本上跑一遍dotnet ef database update做验证,不要只在本地开发库跑通就完事。

多ORM混用时,事务的一致性靠的是“同一个连接 + 同一个事务对象”。但很多团队会在服务层创建两个仓库对象,各自new了一个连接,事务就失效了。解决方法是引入工作单元模式(UnitOfWork),同时在IoC容器里注册同一个IDbConnection,确保一次请求一个连接。

我实测下来,混用ORM最稳定的组合,是“一个全功能ORM负责写与事务,一个微ORM负责读与查”,而不是两个全功能ORM拼在一起,那样只会让事务和迁移的管理复杂度翻倍。


最后分享一点个人体会:ORM选型没有标准答案,但有一点是共通的——用的人必须清楚手里这个工具在每个环节到底在做什么。EF Core会在你不知道的地方生成SQL,Dapper会把SQL执行和映射的掌控权全部交给你,SqlSugar则在两者之间提供了一条“好上手”的路线。我在实际项目里吃过不少亏,最大的那次就是从EF Core一把梭到线上复杂查询超时,最后靠Dapper重写才救回来。也是从那之后,我才开始认真做功能拆解和性能测试,不再迷信某个框架的“万能”风评。

如果你正在纠结选型,我的建议是:拿真实业务中最复杂的一个查询,在候选ORM里各写一遍,再跑一次简单基准测试。哪一款让你写起来舒服、查起来可控、维护起来不慌,就选哪一款。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询