恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
.NET Core集成SqlSugar实战:从CRUD到性能优化
首页
资讯中心
/
.NET Core集成SqlSugar实战:从CRUD到性能优化
.NET Core集成SqlSugar实战:从CRUD到性能优化
发布时间:2026/10/19 2:58:10
做.NET后端的小伙伴应该都有过这样的纠结数据访问层到底用哪个框架项目排期紧、团队水平参差不齐、数据库还可能是MySQL和SQLServer混着用这种情况下EF Core的全套迁移机制虽然正规但遇到复杂查询和批量写入反而显得笨重。我自己在几个项目里反复折腾最后基本固定用 .NET Core 集成 SqlSugar 这套组合——轻量、开箱即用、文档简洁从接入到跑通第一个查询基本半小时以内。这篇不是官方文档翻译是我把SqlSugar塞进.NET Core项目的实战记录涉及选型思考、配置细节、核心CRUD、事务分页以及一堆只有真跑过才知道的坑适合正打算引入SqlSugar、或者被EF Core和Dapper选择困难症困扰的朋友。1. 内容整体设计与思路拆解1.1 最简集成到底“简”在哪里“开箱即用”这四个字不是随便说的。传统ORM集成要解决的事情其实不少包引用、上下文注册、实体映射、基类封装、特殊类型映射比如枚举、Json、时间类型、SQL日志链路。SqlSugar的集成之所以能压缩到如此简单核心在于几个很聪明的设计。SqlSugarScope这个入口对象直接提供了完整的增删改查API不需要像某些重量级ORM那样先建一个上下文基类、再暴露一堆DbSet、还要维护迁移历史表。它把最常见的数据库操作全部收敛到一个接口上开发者拿到ISqlSugarClient就可以开始写业务代码。再加上SugarTable和SugarColumn这两个实体特性映射主动权完全放在模型上想改表名、列名、主键、自增、可空属性直接在实体类上标注就行。配合CodeFirst模式还能由实体直接生成数据库表开发环境下尤其省事。所以整个接入流程可以被压缩成四个动作装包、写配置、定义实体、注入服务。也没有额外的代码生成器依赖没有复杂的启动配置要求。我最早在一个排期特别紧的模拟项目X里用它从新建项目到接口返回第一条数据库记录大概只花了二十分钟。当时的感受就是这玩意儿设计的时候真的是站在写业务的人角度去考虑的。1.2 为什么是SqlSugar而不是EF Core或Dapper说实话我早期项目用的是EF Core后来维护老项目又用过Dapper。这两个框架各有明确的定位但也各有让人难受的点。EF Core适合模型复杂、团队愿意严格遵守迁移规范的长期项目它的LINQ表达式树确实强大可隐式加载、ChangeTracker这些概念在并发高的读写场景里很容易埋雷。你永远要记得AsNoTracking()记得处理导航属性的懒加载记得每次改实体后生成Migration。在小团队快节奏迭代的场景下这些心智负担会被放得很大。Dapper性能确实强但连个最简单的Insert都要手写SQL。业务一旦复杂起来就是一堆字符串拼接、参数对象、result map。SqlSugar的定位很聪明它把Dapper的高性能和语法糖的易用性做了个折中。单表查询的Lambda语法比EF更短更直接多表联查又允许你直接写原生SQL而且一套接口能兼容多种数据库。团队里不管是新手还是从别的ORM转过来的老手基本一天内都能上手写代码。从架构角度说它尤其适合中小型业务系统比如内部管理系统、报表平台、API服务。功能齐全但没有过于沉重的框架概念负担。我不是说EF Core不好而是说在一半以上实际项目中SqlSugar的性价比确实更高。1.3 这个选型对项目架构的真实影响技术选型影响最大的是数据访问层的封装模式。你选了SqlSugar之后仓储接口、事务外层、甚至多租户方案都会围绕ISqlSugarClient来设计后续所有数据库相关代码都会以它为中心。我实际接过一个多库项目同一个服务进程里同时连MySQL和SQLServer靠ConfigId做隔离SQL日志在AOP里统一输出。如果当时自己封装DbHelper光是连接管理和参数适配就得写不少代码。SqlSugar把这块边界处理得比较干净接入成本低后面换库或者加库时改动也集中在配置层。所以如果你正在规划新项目并且不想引入太重的ORM层这条路线非常值得先做一个技术验证。2. 环境准备与基础配置2.1 项目搭建与NuGet包安装首先创建一个ASP.NET Core Web API项目。当前用.NET 8的居多.NET 6和.NET 7也完全没问题SqlSugarCore这个包对这几个版本都支持得不错。在NuGet包管理器里搜索SqlSugarCore。注意不是老版本的SqlSugar那个是给传统.NET Framework用的Core版才对应.NET Core/.NET 5。安装它会自动拉取一些底层数据库驱动的依赖包比如连接MySQL需要用到的驱动。用命令行的方式更直接dotnet new webapi -n SugarDemo cd SugarDemo dotnet add package SqlSugarCore装完包以后建议在项目里再建一个文件夹专门放数据库相关的代码比如Data或者Infrastructure。我自己习惯把连接工厂、实体类、初始化逻辑都放在这个目录下后面维护起来不会到处都是数据库代码。2.2 连接串配置与数据库初始化连接串写在appsettings.json里这是最常规的做法。以MySQL为例{ ConnectionStrings: { MainDb: Server127.0.0.1;Port3306;Databasesugar_demo;Uidroot;Pwd123456;CharSetutf8mb4; } }如果用的是SQLServer连接串格式会不同Server.;DatabaseSugarDemo;User Idsa;Password123456;EncryptTrue;TrustServerCertificateTrue;务必将CharSetutf8mb4这类字符集参数加上不然遇到中文和特殊符号容易出现乱码或存储问题。然后准备一个静态类来做数据库连接工厂或者直接在Program.cs里注册服务。我建议先写一个简单的工厂类把连接配置集中起来后面多环境切换时只改这一处。2.3 SqlSugarScope的单例模式选择很多老博客会让你把SqlSugarClient注册为AddScoped这个做法在新版本里已经不建议了。新版官方推荐的用法是注册SqlSugarScope并且以单例模式注入。原因很简单SqlSugarClient本身不保证线程安全在并发请求下可能会出现连接串用、事务交叉的诡异问题。而SqlSugarScope通过内部上下文隔离解决了这个隐患。Program.cs里的注册代码builder.Services.AddSingletonISqlSugarClient(sp { var config builder.Configuration.GetConnectionString(MainDb); return new SqlSugarScope(new ConnectionConfig { DbType DbType.MySql, ConnectionString config, IsAutoCloseConnection true, InitKeyType InitKeyType.Attribute }); });IsAutoCloseConnection设置为true后每次操作结束会自动关闭连接省去手动释放的麻烦。InitKeyType.Attribute表示主键、自增等关键信息从实体特性中读取。这里不用设置ConfigureExternalServices除非你要自定义序列化或者依赖注入某些服务。注册成单例后在控制器或者服务类里直接注入ISqlSugarClient就能用完全不需要操心并发连接混乱的问题。这一步是整个集成过程中最关键的选择做对了后面省心很多。3. 核心实操实体设计、CRUD、分页、事务3.1 实体类设计与CodeFirst建表SqlSugar的实体类通过特性来标注映射信息。以一张订单表为例[SugarTable(t_order)] public class OrderEntity { [SugarColumn(IsPrimaryKey true)] public long Id { get; set; } [SugarColumn(Length 64)] public string OrderNo { get; set; } public decimal Amount { get; set; } public int Status { get; set; } [SugarColumn(IsNullable true)] public DateTime? CreateTime { get; set; } }SugarTable指定表名。我还习惯给表加前缀比如t_order避免和数据库关键字冲突SQLServer里Order这种名字不打括号根本没法用。SugarColumn里常用的几个配置IsPrimaryKey主键标记。用雪花ID或者自增ID都行我倾向用bigint雪花ID分布式环境下不用依赖数据库自增。IsIdentity数据库自增列配合主键使用。Length字符串长度CodeFirst建表时会生成对应长度的varchar。IsNullable是否允许为空。不写默认是false。ColumnName指定数据库列名和属性名不一致时用。CodeFirst初始化一般放在程序启动阶段或者第一次运行时执行db.DbMaintenance.CreateDatabase(); db.CodeFirst.InitTablesOrderEntity();CreateDatabase()会先创建数据库实例如果已经存在则跳过。InitTables会检查实体对应的表是否存在不存在就创建存在则只对比列差异自动补充缺失字段不会清空数据。开发环境这么用很方便生产环境还是要谨慎即使我用了这个功能也会在发布前确认好变更脚本。3.2 增删改查的最简写法插入单条记录var order new OrderEntity { OrderNo NO20250101, Amount 99.9m, Status 1, CreateTime DateTime.Now }; int rows db.Insertable(order).ExecuteCommand();ExecuteCommand返回受影响行数。插入列表时有一个常见的性能误区不要用循环逐个Insert直接传List进去做批量插入。ListOrderEntity orderList BuildOrderList(); int rows db.Insertable(orderList).ExecuteCommand();SqlSugar的批量插入默认会分批拼接SQL速度比循环快一个数量级几千条数据基本秒入。查询的核心API是Queryable几乎可以覆盖所有单表查询场景var list await db.QueryableOrderEntity() .Where(o o.Status 1) .OrderBy(o o.CreateTime, OrderByType.Desc) .ToListAsync(); var one await db.QueryableOrderEntity() .FirstAsync(o o.OrderNo NO20250101); var byId await db.QueryableOrderEntity() .InSingleAsync(10001);InSingleAsync按主键查单条查不到会返回null。查询条件里也可以直接写o o.Amount 50 o.Status 1支持常见的比较和逻辑组合。更新和删除也走的是链式API// 根据实体主键更新 db.Updateable(order).ExecuteCommand(); // 指定列更新 db.UpdateableOrderEntity() .SetColumns(o o.Status 2) .Where(o o.Id 10001) .ExecuteCommand(); // 按主键删除 db.DeleteableOrderEntity(10001).ExecuteCommand();这些写法最大的好处是统一不需要像Dapper那样为每个方法写SQL和参数映射。3.3 联表查询与分页多表查询是业务系统里绕不开的场景。SqlSugar的联表查询写起来比EF简洁很多var list await db.QueryableOrderEntity() .LeftJoinUserEntity((o, u) o.UserId u.Id) .Where((o, u) u.Status 1) .Select((o, u) new OrderDto { OrderNo o.OrderNo, UserName u.Name, Amount o.Amount }) .ToListAsync();这里Select可以直接输出到DTO不需要先查实体再映射。LeftJoin、InnerJoin都支持多个表继续往后接就行。如果你更喜欢写SQLSqlSugar也允许var list db.SqlQueryableOrderDto( SELECT o.OrderNo, u.Name as UserName FROM t_order o LEFT JOIN t_user u ON o.UserId u.Id ).ToList();分页是另一个高频场景用ToPageListAsync最省事RefAsyncint total 0; var pageList await db.QueryableOrderEntity() .Where(o o.Status 1) .OrderBy(o o.CreateTime, OrderByType.Desc) .ToPageListAsync(pageIndex, pageSize, total);pageIndex从1开始total是总记录数通过引用参数返回。这里有个版本差异老版本直接传ref int total新版本用RefAsyncint包装具体看你的SqlSugarCore版本。如果版本较新也可以试试ToPagedListAsync它返回的是一个包含Items和TotalCount的对象我个人觉得这个语义更清晰。3.4 事务处理与异常回滚事务在SqlSugar里的写法相当直观。同一个db实例上执行多个操作然后用BeginTran和CommitTran包裹try { db.Ado.BeginTran(); db.Insertable(order1).ExecuteCommand(); db.Insertable(order2).ExecuteCommand(); db.Ado.CommitTran(); } catch (Exception ex) { db.Ado.RollbackTran(); // 记录日志 throw; }SqlSugar还提供了语法糖UseTran把事务代码包在一个委托里连手动提交回滚都省了var result db.Ado.UseTran(() { db.Insertable(order1).ExecuteCommand(); db.Insertable(order2).ExecuteCommand(); }); if (result.IsSuccess) { // 事务已提交 } else { // result.Error 里是异常信息 }异步场景对应UseTranAsync。有一点必须提醒默认的BeginTran只对当前连接生效跨库事务不能用这种方式解决多库场景需要引入分布式事务或者其他方案。4. 进阶实操细节与性能优化4.1 AOP拦截SQL日志与慢查询监控SqlSugar提供了一套AOP事件可以无侵入地拿到每次执行的SQL和参数。这个能力在排查问题时特别有用强烈建议一上来就配置好。在注册SqlSugarScope时第二个参数接收一个Action委托专门用来配置AOPbuilder.Services.AddSingletonISqlSugarClient(sp { var config builder.Configuration.GetConnectionString(MainDb); return new SqlSugarScope(new ConnectionConfig { DbType DbType.MySql, ConnectionString config, IsAutoCloseConnection true, InitKeyType InitKeyType.Attribute }, db { db.Aop.OnLogExecuting (sql, pars) { // 这里可以做SQL格式化也可以直接输出 Console.WriteLine($SQL: {sql}); }; db.Aop.OnDiffLogEvent diff { // 数据变更日志写审计非常方便 Console.WriteLine($变更表: {diff.BusinessData}); }; }); });OnLogExecuting在SQL执行前触发很适合记录慢SQL。我一般会在这里加一个时间判断如果某条SQL执行超过500毫秒就输出警示日志。有个细节pars是参数列表如果直接输出sprintf不出来的话SqlSugar提供了一个工具方法UtilMethods.GetSqlString(pars, sql)可以把参数拼成可读的SQL。排查问题的时候这句特别好用。4.2 CodeFirst建表和正式环境迁移策略SqlSugar的CodeFirst本质上是“建表同步器”不是完整的迁移工具。它在开发环境的价值非常大尤其在需求还没完全定下来、字段一天改三次的阶段改实体再跑一次启动就行表结构自动补齐。但是生产环境的表结构变更我还是建议用正式迁移工具或者手写SQL脚本。原因很简单线上表有存量数据CodeFirst同步列只能处理“加列”这种温和场景遇到删列、改类型、加索引这类破坏性变更靠它自动处理是不安全的。我的做法是启动时只在开发环境执行CreateDatabase和InitTables生产环境通过独立的初始化任务或者发布管道里的SQL脚本处理。这样既享受CodeFirst的便利又不至于在线上埋雷。4.3 数据缓存与NoTrackingSqlSugar有一个轻量级查询缓存直接在链上调用WithCache即可var dictList await db.QueryableDictionaryEntity() .WithCache(300) .ToListAsync();上面的语句把查询结果缓存300秒。适合字典表、配置表这类低频变更的数据能显著降低数据库压力。更新这些表之后记得调用db.DataCache.RemoveDataCache();让缓存失效。这里要说明一下和EF Core的不同SqlSugar的查询结果默认就是非追踪的查出来就是普通实体不需要像EF那样特意AsNoTracking()。所以在性能心智上省了一截不需要时刻提醒自己“查完要分离实体”。原生SQL请求也是一个常用兜底手段。复杂的报表统计用Ado非常直接var result await db.Ado.SqlQueryAsyncUserStatsDto( SELECT u.Id, COUNT(*) AS OrderCount FROM t_user u LEFT JOIN t_order o ON u.Id o.UserId GROUP BY u.Id );4.4 多库、读写分离与Json字段处理多库场景在SqlSugar里通过ConfigId来区分。在ConnectionConfig里加上ConfigId标识然后通过多租户接口切换// 配置第二个库 var connectionConfigs new ListConnectionConfig { new ConnectionConfig { DbType DbType.MySql, ConnectionString config, ConfigId main }, new ConnectionConfig { DbType DbType.SqlServer, ConnectionString sqlServerConfig, ConfigId second } }; var db new SqlSugarScope(connectionConfigs); var secondDb db.AsTenant().GetConnectionScope(second);切换库之后用法和主库完全一样业务侧只需要注入一个客户端选哪个库由代码决定。Json字段映射是SqlSugar比较讨喜的一个能力。如果实体属性是一个List或者自定义对象数据库列类型是json只需要在属性上加特性[SugarColumn(IsJson true)] public ListSkuItem SkuList { get; set; }插入和查询时SqlSugar会自动完成序列化和反序列化不需要手写JSON转换。这一点相比某些传统ORM要手动配置值转换器简单太多。5. 常见问题与排查技巧实录5.1 并发时报“连接已被占用”或者“connection already open”这个问题我见过太多次尤其是从EF Core转过来的团队。症状是高并发下随机出现连接异常时好时坏重启服务后又恢复正常。排查思路先看服务注册方式。如果是AddScopedISqlSugarClient或者每次都new SqlSugarClient()并且这个实例被多个异步操作共用就很可能踩到线程安全问题。SqlSugarClient不是线程安全的。解决方案就是前面说的统一使用SqlSugarScope并且注册为Singleton。这是官方文档一直强调的推荐姿势。如果项目里已经有大量new出来的客户端至少要把所有入口收敛到一个注入点上。5.2 MySQL的datetime默认值报错CodeFirst建表时如果实体属性是DateTime且没有给默认值MySQL可能会报默认值相关的SQL异常。我的习惯是允许为空的日期字段用DateTime?不允许为空的在实体构造函数里给默认值或者数据库列设置CURRENT_TIMESTAMP另外MySQL连接串一定记得加CharSetutf8mb4。如果建表后发现中文变成乱码大概率是字符集没有统一检查连接串和表本身的字符集设置。5.3 大分页性能差ToPageList的机制是先count一次再查数据执行两条SQL。几十万行数据以后深分页会越来越慢。如果分页查询集中在后台列表并且数据量很大可以考虑两个优化方向用db.Ado.GetDataTable手写分页SQL配合索引优化。前端加条件过滤减少深分页的触发概率。如果用的是SQLServer可以考虑Offset分页替代Skip模式SqlSugar底层已经处理了但要注意给排序字段加索引。这里也要提醒SqlSugar默认count查询数据如果不需要总行数可以直接用.ToListAsync()然后截取但这对分页组件不友好。按需取舍。5.4 实体名和数据库保留字冲突Order、User、Group这些词在SQLServer里都是保留字。如果实体名恰好叫Order生成的SQL会带出语法错误。我的规避办法很简单所有表名强制加前缀比如t_order、t_user。实体上的SugarTable写清楚表名查询时就不容易踩保留字坑。如果列名也有冲突可以用SugarColumn(ColumnName order_no)指定。总之约定大于配置一开始就制定起名规范会省很多事。5.5 事务里混用异步和同步方法SqlSugar的事务对象在同步和异步之间切换时偶尔会有“当前上下文不存在”的问题。解决方法是同一个事务块内要么全部用同步要么全部用异步不要写一半ExecuteCommand()一半ExecuteCommandAsync()。这个看着是小问题但真踩到的时候排查起来挺费时间的记在这里算是个提醒。结尾这套组合我在几个内部项目里用得相当顺手最直观的感受就是开发效率高踩坑后有清晰文档可以查社区里遇到的问题也基本都有答案。如果你正处在EF Core、Dapper和SqlSugar之间犹豫建议先按这个最简流程搭一个Demo把CRUD、分页、事务跑一遍实际上手十分钟比看十篇对比文章更有效。最后分享一个小操作习惯接入SqlSugar后我会第一时间把AOP日志打开并且在一开始就把数据库连接字符串放到环境变量或者配置中心里而不是硬编码在代码中。前者影响你排障的速度后者影响换环境的成本。控制好这两个点后续项目越大越能体会到基础工作做得扎实的好处。