做.NET开发的这几年我最大的感受是数据库访问这块往往不是“不会写”而是“写着难受”。你可能已经熟练掌握了各种SQL但在业务代码里反复拼接字符串、到处用DataTable、换一种数据库就要重写一大片代码——这种状态一旦上了规模维护成本会指数级上升。今天要聊的SqlSugar就是C#生态里一个帮助开发者把数据库访问这件事理顺的ORM类库。它的核心目标不是给你一万种炫技的API而是让你用最简单直观的方式完成CRUD、分页、联表、事务这些日常高频操作同时保留手写SQL的兜底能力。这篇文章的内容主要来自我在实际项目中把一套基于Web的业务管理后台从裸SQL切换到SqlSugar之后的经验整理。我不会只贴官方文档里那些示例代码更多是讲清楚每一个设计选择背后的原因什么场景适合用它、哪些写法在真实环境里会踩坑、批量操作的性能到底差多少、事务和过滤器怎么组织才不容易出问题。适合正在选型ORM、或者已经在用SqlSugar但想看一些深度实践的同学参考。1. 为什么说SqlSugar值得放进常用类库清单1.1 从手动连接数据库到ORM到底解决了什么早年写数据访问层大部分时间消耗在重复劳动上。每次写一个查询都要经历“打开连接—创建命令—填充DataTable—逐行转实体—释放连接”这一整套流程。单个方法写起来不算难但一个系统里几十张表、几百个查询代码量就非常可观了。更麻烦的是很多团队习惯用字符串拼接动态条件一个不严谨的where条件拼出来轻则查询性能低下重则给SQL注入留了入口。ORM解决的不只是“少写几行代码”的问题它把“关系数据表”和“业务对象”之间的映射交给框架处理让开发者把主要精力放在业务规则上。SqlSugar在这一点上做得相当直接它不需要你配置复杂的映射文件实体类的特性标注基本就能搞定也不需要你在查询里写满满的.Include()链式调用一个.QueryableT()加上条件、排序、分页语义非常清楚。用我自己的话说它像一个“聪明的SQL生成器”帮你在编译期检查表名列名的拼写在运行期生成参数化SQL来规避注入风险。1.2 和Dapper、EF Core对比SqlSugar的差异化定位很多人在选型时会在三种方案里纠结Dapper、EF Core、SqlSugar。简单来说三者的侧重点完全不同。Dapper本质上是一个轻量级的对象映射器它不帮你生成SQL但执行速度极快适合团队里SQL功底扎实、追求极致性能的场合。EF Core是微软生态下的全功能ORM模型关联、迁移、状态跟踪都很完善但学习曲线更陡复杂查询生成的SQL有时候需要调优才能达到预期。SqlSugar则刚好卡在两者中间偏实用的位置。它的语法比Dapper更贴合业务员开发习惯比如分页、多表Join、子查询这些高频操作都有现成的API它没有EF Core那么重的状态跟踪机制模型和数据库之间也不强制要求Code First或DbFirst反过来正过去都可以。对中小企业内部系统和业务后台这类项目来说SqlSugar的上手成本比EF Core低不少写出来的代码又比Dapper直观得多。当然如果你所在团队已经在EF Core基础上沉淀了一套成熟模式也没有必要强行替换。工具是服务业务的选型标准永远是团队水平和项目阶段最匹配的那个。1.3 适合接入的场景和不建议硬上的地方从我实际用下来的经验看SqlSugar最适合的几类场景包括Web管理系统和管理后台、内部信息化系统、大部分常规数据展示与报表页面以及快速迭代的互联网业务后端。这些场景的核心特征就是“常规操作占绝大多数”而且交付节奏比较快需要一个能快速生成查询代码、同时容易出问题的概率也低的框架。但不建议硬上的情况也有几种。如果你手头的项目极其依赖原生产SQL来优化每一条查询或者团队已经建立了一套很强的数据库存储过程体系那引入ORM反而会形成一层“翻译损耗”。另外如果项目需要非常复杂的跨数据库事务一致性协调那这类分布式问题本身就不是任何一个ORM能替你解决的。SqlSugar是一个数据访问工具不是架构银弹它能帮你解决90%的“怎么写数据库代码”的问题但剩下10%的架构决策还是得靠你对业务的理解来补位。2. 环境准备与最快的接入方式2.1 NuGet包引入与框架兼容性SqlSugar针对不同.NET版本提供了对应版本的包。如果是传统.NET Framework项目使用.NET Framework 4.6以上的版本基本都能兼容如果是.NET Core或更高版本直接引用SqlSugarCore即可。标准做法是在项目根目录执行包安装命令安装完成后会自动拉取相关依赖不需要手动配置额外的运行时组件。装完之后建议顺手看一眼项目的目标框架确保包版本和运行时匹配避免出现程序集加载异常。2.2 连接配置与客户端创建方式使用SqlSugar的第一步是创建一个数据库访问客户端所有查询和操作都通过这个入口完成。它的连接配置是一个连接配置对象里面常用属性包括连接字符串、数据库类型、是否自动释放连接等。实际操作中我推荐使用“单例”方式创建这个客户端也就是整个应用生命周期里只初始化一次后续所有请求复用同一个实例。有人可能会担心单例会带来线程安全问题这一点SqlSugar处理得比不少框架要好它的内部实现了线程隔离和连接池管理。真正要注意的是连接字符串里的连接池配置以及连接对象的自动释放开关。我一般会把连接释放设为自动打开这样每次操作执行完毕框架内部会归还连接避免因为忘记关闭连接导致的连接耗尽问题。共享同一个客户端实例在Web应用里还有一个额外好处你可以在一个地方统一配置过滤器、AOP插件和日志记录而不是每个控制器里各自new一套。2.3 第一个实体类与基础CRUD有了客户端之后接下来就是把数据库表和C#实体类对应起来。SqlSugar支持通过特性主动标注表名和列名也支持约定的自动映射。我的习惯是显式标注因为数据库里经常会出现一些风格不一致的命名显式标注能让后续维护的人一眼看明白实体和表之间的对应关系。举个例子一个用户表对应的实体类大致长这样[SugarTable(sys_user)] public class SysUser { [SugarColumn(IsPrimaryKey true)] public long Id { get; set; } public string UserName { get; set; } public int Age { get; set; } public string DeptName { get; set; } public bool IsDeleted { get; set; } public DateTime CreateTime { get; set; } }基础CRUD几乎就是一行式操作。插入通过插入方法提交单个对象或对象列表更新通过更新方法按主键定位删除既支持按主键删除也支持按条件删除查询用查询方法配合Lambda表达式定位条件。这一套API设计得非常统一核心思想就是“你想对数据做什么调用对应方法把条件写清楚剩下的交给框架”。下面这个示例涵盖了最常见的四个操作// 新增 var user new SysUser { UserName 张三, Age 20, CreateTime DateTime.Now }; db.Insertable(user).ExecuteCommand(); // 更新 user.Age 21; db.Updateable(user).ExecuteCommand(); // 删除 db.DeleteableSysUser(user.Id).ExecuteCommand(); // 查询 var list db.QueryableSysUser() .Where(u u.Age 18) .OrderBy(u u.CreateTime, OrderByType.Desc) .ToList();初次上手的人可能会觉得这些方法名太“直白”不像某些框架那样需要绕很大一圈。但正是这种直白的命名让团队里的新人在半天之内就能上手极大地降低了沟通成本。3. 高频查询场景的实操与细节拆解3.1 条件查询的多种姿势不要只会拼接字符串查询功能是ORM使用频率最高的部分SqlSugar的查询API支持条件直接写在Lambda表达式里。这里我想强调一个关键习惯能写表达式就写表达式尽量少用字符串条件。原因很简单表达式最终会被解析成参数化SQL既能防SQL注入也能让数据库缓存执行计划提升重复查询的命中率。实务中经常遇到一类场景筛选条件不是固定的用户可能选了状态、可能选了时间段、也可能什么都没选。直接写一串if去分支拼接查询对象会显得十分臃肿这时候推荐用条件方法由它内部判断条件是否成立成立才将条件追加到查询中。比如下面这段代码var query db.QueryableSysUser() .WhereIF(!string.IsNullOrEmpty(name), u u.UserName.Contains(name)) .WhereIF(startTime.HasValue, u u.CreateTime startTime.Value) .WhereIF(endTime.HasValue, u u.CreateTime endTime.Value);这样写的好处是无论用户怎么操作查询逻辑都保持线性增长不引入一堆临时变量。另外当条件里需要判断集合包含关系时可以直接使用预置函数来处理框架会生成对应的多条参数化SQL效率上也没问题。3.2 分页查询的正确打开方式与总数获取分页是几乎所有后台系统的标配功能。SqlSugar提供了直接支持分页的方法核心用法是传入页码和页大小并输出总记录数。有一个很小的细节容易踩坑如果你先单独执行一次Count()再执行一次分页查询就需要访问两次数据库白浪费一次往返而直接用分页方法框架会把“总数统计”和“当前页数据查询”合并成一趟完整的查询性能更优。下面这个写法也是后续接口编码里最常用的模板int pageIndex 1; int pageSize 20; int total 0; var pageList db.QueryableSysUser() .Where(u u.DeptName 研发部) .OrderBy(u u.CreateTime, OrderByType.Desc) .ToPageList(pageIndex, pageSize, ref total); // total 即为符合条件的总记录数需要注意的是分页查询必须搭配稳定的排序。如果排序字段存在大量重复值比如同一个创建时间的记录数量比单页还多那分页结果在不同请求之间可能出现重复或遗漏。解决办法是追加一个辅助排序字段比如主键倒序这样能保证排序结果的稳定性。3.3 多表联查与投影避免把整个表搬进内存后台系统里“查人带部门、查订单带用户名称”这类需求极其常见。SqlSugar的联查语法比较接近大家熟悉的SQL语义使用左连接、内连接等方法连接不同实体然后通过选择方法把结果映射到视图模型。很多初学者喜欢直接把多表数据全部查出来再在内存里做关联这在数据量小的时候问题不大可一旦单表数据过万这种打法不仅慢还白白占用服务器内存。投影是解决这个问题的关键。所谓投影就是只选择我们真正需要的字段到目标对象里。比如查询用户列表时我只想拿到用户ID、姓名和部门名称那就在执行关联后通过选择方法把结果集映射成精简的视图模型对象。SqlSugar翻译出来的SQL里有明确的字段列表不会出现明显的全列查询。这一点对数据库性能有本质改善尤其是当表里存在大字段比如长文本、二进制数据时少查一个字段可能就少读几十上百KB数据。3.4 子查询与SQL直出复杂场景下的兜底方案虽然ORM能覆盖绝大多数业务查询但总有一些极端复杂的需求比如嵌套统计、跨库关联、或是某个报表SQL已经由DBA优化到了极致再翻译成Lambda表达式反而画蛇添足。SqlSugar在这种时候提供了两个解决方案一是支持在查询条件里直接嵌套子查询表达式二是允许通过底层接口手写完整SQL。我个人比较推荐的策略是“二八原则”80%的场景使用查询API20%真正绕不过去的复杂查询使用原生SQL并且把原生SQL封装在数据仓储层内部不扩散到业务逻辑层。通过底层接口执行原生SQL的方式非常简单支持参数化查询也支持直接映射成实体或动态对象。例如var result db.Ado.SqlQuerydynamic( SELECT u.Id, u.UserName, d.DeptName FROM sys_user u LEFT JOIN sys_dept d ON u.DeptId d.Id WHERE u.Age age, new { age 20 } );这里用命名参数占位的写法既方便维护又能避免字符串拼接带来的注入风险。一个实际经验是不要试图把每一条SQL都翻译成ORM写法有些优化良久的原生SQL在ORM里改来改去最后生成的语句反而更难让数据库优化器满意。保留一条“逃生通道”往往比强迫自己适应框架更重要。4. 更新、删除与事务的正确组织方式4.1 更新操作的字段控制避免误更新大字段更新操作看起来只是简单的数据提交但在实际业务里很容易出现“多更新了不该更新的列”的问题。比如一个用户编辑功能页面只允许修改姓名但如果你直接把整个实体对象提交更新那创建时间、状态等字段可能也会被覆盖。解决方案是使用更新方法带来的字段筛选手段包括忽略指定列或者反过来只更新指定列。下面这个例子展示的是“只更新姓名和年龄”的场景db.UpdateableSysUser() .SetColumns(u new SysUser() { UserName 李四, Age 22 }) .Where(u u.Id 1) .ExecuteCommand();使用这种方式框架生成的UPDATE语句只会包含指定的字段不会把实体中其他未赋值的属性也一并提交。这个写法在对接前端只传局部数据的接口时特别有用可以有效避免“空值覆盖”的经典事故。4.2 批量操作性能对比循环提交是最差的选择很多新手在写入报表数据时习惯用一个foreach循环不停执行插入方法单条插入的写法确实简单但在数据量稍大的场景下性能惨不忍睹。每条插入操作都涉及一次数据库往返、一次SQL解析和一次事务提交假设插入1000条数据循环方式可能耗费数秒钟而批量插入通常只需要几十毫秒到几百毫秒。SqlSugar的批量插入和批量更新都是原生支持的内部会生成高效的批量SQL语句甚至支持做一定程度的分批处理。实际测试过一个模拟场景向同一张表插入一万条记录循环单条插入耗时要两三分钟级别批量插入只花了几秒。所以我强烈建议只要数据量超过几条就坚决使用批量API不要偷懒写循环。同理批量更新也可以传入一个实体列表框架会根据主键自动逐条更新。4.3 事务里最容易忽视的细节在业务系统中涉及多张表的一致性写入时必须使用事务。SqlSugar的事务使用方式是提供一个入口传入需要原子性执行的代码块。代码块内任何一步抛异常整体事务就自动回滚这比手动提交、手动回滚要稳妥得多。这里我想重点提醒一个实际坑事务代码块内部不要再去做与当前事务无关的长耗时操作比如调用外部接口、写日志文件等。原因是数据库事务会长时间持锁拖得越久死锁和超时的概率就越高。正确的做法是把所有外部调用尽量放在事务提交之后处理事务内部只做必要的数据库读写。如果系统确实需要更复杂的“先做事再落库”的模式则要考虑引入最终一致性方案而不是单纯堆数据库事务。4.4 全局过滤器逻辑删除和多租户的优雅实现不少业务系统中数据删除并非物理删除而是通过标记位实现“逻辑删除”例如IsDeleted字段。如果每次查询都手动加Where(x !x.IsDeleted)代码会变得重复繁琐也容易遗漏。SqlSugar提供了全局查询过滤器可以给指定实体统一追加过滤条件。举个例子对用户表启用逻辑删除过滤db.QueryFilter.AddTableFilterSysUser(u u.IsDeleted false);配置完之后所有针对用户表的查询会自动追加“未被删除”的条件不需要再手动写。这个方法在“按租户隔离”的场景中也很有用比如多租户系统里每张业务表都有一个租户ID给表注册过滤器后任何查询都只会返回当前租户的数据从数据访问层就杜绝了越权风险。它是榨干ORM价值的一个典型能力值得认真用起来。5. 踩坑记录与性能排查心得5.1 主键选择与数据库默认值问题SqlSugar默认会把标记为主键的属性识别为自增列。但实际项目中主键策略五花八门有的是数据库自增有的是应用层生成雪花ID有的直接用Guid字符串。这几种策略各有各的注意点。如果主键由数据库生成那插入的实体对象主键属性不应该赋值否则容易触发异常的SQL如果使用雪花ID通常需要在插入前由代码生成并赋给实体。还有一种常见问题是“主键冲突”在高并发写入时使用了简单的Guid片段或时间戳拼接出现重复的概率虽然低但并非没有。我的建议是生产环境尽量使用成熟的全局唯一ID生成算法而不要依赖简单的随机值。主键策略看似一个小问题但一旦上线后出现重复改造成本会非常高。5.2 字段映射和命名冲突的坑数据库列名有时候会取一些比较通用的名字比如Description、Name甚至可能与SQL关键字冲突。实体类上的标注特性带了自定义列名能力可以解决这类问题。另外不同类型数据库的字段类型映射也值得留意比如bool类型在SQL Server里映射为bit在MySQL里映射为tinyint(1)如果迁移数据库某些隐式类型转换可能会造成不一致。我在项目里还遇到过一个问题查询结果映射到实体时某个可空类型字段始终赋不上值排查下来发现是数据库列类型被定义成了非空字符串而实体属性是空字符串类型两者在底层转换时出现了不匹配。最终通过调整实体属性的可空标注才解决。建议在模型设计阶段数据库字段的可空性与实体可空性尽量保持一致能规避掉大量隐性问题。5.3 大数据量场景下的查询性能排查很多人认为用了ORM就万事大吉其实ORM生成的SQL不总是最优的。排查性能问题第一步永远是“看它生成的SQL长什么样”。SqlSugar提供了SQL日志输出能力可以通过AOP接口把每次执行的SQL打印出来。拿到SQL之后再结合数据库执行计划去分析索引有没有被正确使用。比较常见的性能问题包括隐式类型转换导致索引失效、深分页时偏移量过大导致扫描大量数据、关联查询时没有合适索引导致临时表排序等。对于深分页问题我的常用手段包括“先查主键再做关联”或“基于游标翻页”这比直接翻到十万条之后要高效很多。另外一个划算的优化是配合索引设计例如频繁按时间和状态联合查询的就建立联合索引但索引也不是越多越好每次写入都会负担维护成本这个平衡需要结合系统读写比例去把握。5.4 连接池与长时间占据连接的问题Web应用与数据库之间的连接池是有限的如果某个请求长期持有连接不释放在并发升高时很容易出现连接耗尽。SqlSugar内部做了不少连接管理的工作但开发者仍然需要注意自己的代码是否无意间阻塞了连接释放。比如在事务内部执行了可能耗时较长的外部HTTP调用就非常容易触发这类问题。我给团队定的规矩很简单事务里尽量不跨线程、不调用远程服务如果实在绕不开就把连接超时和命令超时都显式配置出来并且通过监控关注数据库连接数的变化趋势。正常Web请求的处理时间是比较短的当你发现数据库连接数持续攀升且大量处于Sleep状态时优先检查是不是代码中某个分支忘记释放连接或事务时间过长。不要为了“省事”把连接对象放在静态变量里到处共享这种设计在遇到并发故障时排查起来极其痛苦。写在最后的个人体会把这套框架用顺之后我最大的体会是ORM的价值不在于“帮你省掉所有SQL”而在于把那些重复、机械的数据访问工作收编成框架能力让你把精力放到真正需要动脑的业务逻辑上去。SqlSugar很多设计确实是冲着实际项目里的痛点来的批量操作、过滤器、灵活的条件编排这些能力在日常业务开发里几乎每天都会用到。不过也不要过度依赖任何框架——关键查询始终要去看一眼它生成的SQL是否合理性能问题最终还是要回到数据库本身去解决。按下葫芦浮起瓢的工具很多找到那个最懂业务节奏的帮手比追求“大一统”的时髦架构要实在得多。
阅读完成 · 觉得有帮助?