1. 项目概述:为什么从SqlSugar的基础查询开始?
如果你正在用.NET做后端开发,尤其是涉及到数据库操作,那你大概率听说过或者正在用SqlSugar。它作为一个轻量级的ORM框架,这几年在社区里的热度一直不低,原因很简单:它足够简单,也足够强大,能让你用更少的代码、更直观的方式去操作数据库。但很多朋友,包括我早期也一样,一上来就想搞“高级功能”,比如分库分表、读写分离,结果连最基础的查询都写得磕磕绊绊,各种报错,效率没提上去,反而踩了一堆坑。
所以,今天我们不谈那些花里胡哨的,就扎扎实实地把SqlSugar的基础查询给捋清楚。这就像练武功,马步扎不稳,后面的招式都是花架子。所谓基础查询,核心就是SELECT语句的那些事:怎么查单条、查列表、查数量、按条件查、排序、分页。别看这些操作简单,但里面门道不少,比如First和Single用错了会抛异常,分页参数没处理好性能直接拉胯,还有那个让人头疼的ToListAsync和ToList到底该用哪个。
我见过不少项目,因为基础查询写得随意,导致后期维护困难,性能瓶颈也往往出在这些最常用的操作上。因此,这篇内容我会结合我这些年踩过的坑和总结的经验,把SqlSugar基础查询的每一个细节都掰开揉碎了讲,目标是让你看完之后,不仅能写出正确的查询,更能写出高效、健壮的查询。无论你是刚接触SqlSugar的新手,还是想巩固基础的老手,相信都能有所收获。
2. 核心概念与DbContext初始化
在深入查询之前,我们必须先打好地基。SqlSugar的核心是围绕SqlSugarClient(或ISqlSugarClient)这个对象展开的,现在更推荐使用依赖注入的方式,通过SqlSugarScope来管理生命周期。理解这个“入口”的配置,是避免后续各种连接问题和性能问题的关键。
2.1 理解SqlSugarScope与连接配置
SqlSugarScope是SqlSugar 5.x之后推荐的单例模式核心对象,它内部维护了连接池,能自动管理连接的开启和关闭,比直接使用SqlSugarClient更省心。初始化它,本质上就是告诉SqlSugar:你要连接哪个数据库、用什么账号、以及一些全局的行为规则。
using SqlSugar; public static class SqlSugarHelper { // 使用SqlSugarScope作为单例 public static readonly SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig() { ConnectionString = "Server=localhost;Database=TestDB;User Id=sa;Password=yourpassword;", // 你的连接字符串 DbType = DbType.SqlServer, // 数据库类型,如SqlServer、MySql、PostgreSQL等 IsAutoCloseConnection = true, // 自动关闭连接,设为true最省心 InitKeyType = InitKeyType.Attribute // 实体主键、标识列通过特性(如[SugarColumn])来识别 }); }这里有几个参数必须搞清楚:
- DbType:这是你数据库类型的枚举。填错了,SqlSugar生成的SQL语句就会不对,比如把
GETDATE()生成到MySQL里。常见的值有DbType.SqlServer、DbType.MySql、DbType.PostgreSQL、DbType.Oracle。 - IsAutoCloseConnection:我强烈建议设为
true。这意味着你不需要手动调用Open()和Close(),SqlSugar会在需要时从连接池取连接,用完后自动归还。如果你设为false,就必须自己管理连接生命周期,很容易忘记关闭导致连接泄露。 - InitKeyType:这决定了SqlSugar如何识别实体类的主键和自增列。设为
Attribute是最清晰的方式,通过在实体属性上打[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]这样的标签来声明。另一种方式是InitKeyType.SystemTable,即从数据库系统表读取,但这种方式有局限性,且不如特性声明灵活直观。
注意:在生产环境中,连接字符串绝对不要硬编码在代码里。应该从
appsettings.json、环境变量或配置中心读取。另外,对于.NET 6/7/8的项目,更推荐在Program.cs中使用依赖注入的方式注册SqlSugarScope,这样可以更好地与ASP.NET Core的生命周期集成,也方便进行单元测试。
2.2 实体类映射的“正确姿势”
ORM是“对象关系映射”,所以实体类是重中之重。一个定义清晰的实体类,能让后续的查询、插入、更新操作事半功倍。
[SugarTable("Student")] // 指定表名,如果类名和表名一致,可省略 public class Student { [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] // 主键且自增 public int Id { get; set; } [SugarColumn(Length = 50, IsNullable = false)] // 字段长度50,不可为空 public string Name { get; set; } public int Age { get; set; } [SugarColumn(ColumnName = "class_id")] // 数据库字段名与属性名不同时,用ColumnName指定 public int ClassId { get; set; } [SugarColumn(IsIgnore = true)] // 此属性不映射到数据库 public string DisplayInfo => $"{Name}({Age})"; [Navigate(NavigateType.OneToOne, nameof(ClassId))] // 一对一导航属性(需要联表查询) public Class Class { get; set; } }关键点解析:
[SugarTable]:当你的类名和数据库表名不一致时(比如类叫Student,表叫t_stu),必须用这个特性指明。一致时可省略,但我建议显式写上,代码更清晰。[SugarColumn]:这是最常用的特性。IsPrimaryKey和IsIdentity:必须准确标记。这影响到Insert后能否返回自增ID,以及Update时能否正确识别WHERE条件。Length:对于字符串类型,指定数据库字段长度。不指定时,SqlSugar会有默认值,但为了和数据库设计一致,最好明确指定。IsNullable:指示字段是否允许为NULL。这会影响Insert和Update时SqlSugar的判空逻辑。ColumnName:解决“数据库字段名是下划线风格,C#属性名是驼峰风格”这类命名差异问题。用了它,你就能用student.Name去操作数据库里的name字段。IsIgnore:标记后,这个属性会被SqlSugar完全忽略,不参与任何数据库操作。常用于计算属性、临时字段或敏感信息。
- 导航属性:
[Navigate]用于定义对象之间的关系(一对一、一对多等)。这里有个巨大的坑:导航属性本身不会自动加载数据!它只是定义了一个关系路径。你必须使用Includes或Mapper方法进行联表查询时,数据才会填充到这个属性里。很多新手以为定义了导航属性就能直接访问student.Class.Name,结果发现是null,问题就出在这里。
3. 基础查询操作全解析
地基打牢了,现在我们开始盖房子。基础查询是使用频率最高的操作,SqlSugar提供了链式调用的API,写起来非常流畅。我们从最简单的开始。
3.1 查询单条记录:First、Single与它们的“坑”
当你确定查询条件最多只返回一条记录时(比如用主键查),就需要用单条查询方法。
// 假设我们有一个Student表,Id是主键 var student1 = Db.Queryable<Student>().Where(s => s.Id == 1).First(); var student2 = Db.Queryable<Student>().Where(s => s.Id == 1).Single(); var student3 = Db.Queryable<Student>().Where(s => s.Id == 1).FirstAsync().Result; // 异步版本First()vsSingle(),怎么选?这是最容易混淆的地方,选错了程序就会抛异常。
First():返回序列中的第一个元素。如果序列为空(没查到),则抛出InvalidOperationException异常。如果序列有多个元素,它只取第一个,不会报错。Single():返回序列中的唯一元素。如果序列为空或多于一个元素,都会抛出InvalidOperationException异常。
结论与建议:
- 当你用唯一性条件查询时(如主键
Id == 1),用Single()更安全,因为它能帮你验证“是否真的只有一条”这个假设。如果有人误删了数据或条件写错,Single()会立刻用异常告诉你出问题了。 - 当你进行非唯一性条件查询,或者排序后取第一条时,必须用
First()。例如:Db.Queryable<Order>().Where(o => o.UserId == 1001).OrderByDescending(o => o.CreateTime).First(),获取用户最新订单。 - 异步方法:
FirstAsync()和SingleAsync()是它们的异步版本,在ASP.NET Core等支持异步的上下文中,应优先使用,避免阻塞线程。
实操心得:我个人的习惯是,只要是按主键查,一律用
Single。这相当于加了一道保险。对于其他明确可能返回多条记录的场景,再用First。另外,SqlSugar还提供了FirstOrDefault和SingleOrDefault,它们在找不到元素时返回null而不是抛异常,在业务逻辑允许“查不到”的情况下非常有用,可以避免不必要的异常处理。
3.2 查询列表:ToList的同步与异步抉择
查询多条记录是最常见的场景,对应SQL的SELECT * FROM ...。
// 同步查询:直接返回List<Student> List<Student> listSync = Db.Queryable<Student>().Where(s => s.Age > 18).ToList(); // 异步查询:返回Task<List<Student>>,推荐在Web应用中使用 List<Student> listAsync = await Db.Queryable<Student>().Where(s => s.Age > 18).ToListAsync(); // 带排序的查询 List<Student> orderedList = Db.Queryable<Student>() .Where(s => s.ClassId == 10) .OrderBy(s => s.Age) // 按年龄升序 .OrderByDescending(s => s.Name) // 再按姓名降序(OrderBy之后调用是追加排序条件) .ToList();核心要点:
Queryable<T>():这是查询的起点,它返回一个ISugarQueryable<T>对象,代表一个待执行的查询。Where():指定查询条件,支持丰富的Lambda表达式。SqlSugar会将其转换为对应的SQLWHERE子句。它是延迟执行的,只有在调用ToList、First、Count等方法时,SQL才会真正发送到数据库。OrderBy/OrderByDescending:排序。可以多次调用,相当于SQL中的ORDER BY age ASC, name DESC。ToList()vsToListAsync():这是性能关键。ToList()是同步方法。它会阻塞当前线程,直到数据库返回所有数据。在桌面应用或控制台程序中可能问题不大。ToListAsync()是异步方法。它不会阻塞当前线程,在等待数据库响应时,线程可以被释放去处理其他请求。在ASP.NET Core、Web API等I/O密集型的Web服务器应用中,必须优先使用异步方法。这能显著提高服务器的并发处理能力(I/O密集型),避免线程池线程被大量阻塞导致服务吞吐量下降。
延迟执行(Deferred Execution)的妙用:因为Queryable和Where是延迟执行的,所以你可以在最终执行前,动态构建查询条件。
ISugarQueryable<Student> query = Db.Queryable<Student>(); if (!string.IsNullOrEmpty(keyword)) { query = query.Where(s => s.Name.Contains(keyword)); } if (minAge > 0) { query = query.Where(s => s.Age >= minAge); } // 直到这里,SQL都还没生成 var result = query.OrderBy(s => s.Id).ToList(); // 此时才生成SQL并执行这种方式比在代码里拼接SQL字符串要安全、优雅得多。
3.3 查询数量、是否存在与聚合查询
除了查数据本身,我们经常需要知道有多少条数据,或者做一些统计。
// 1. 查询总记录数 - Count int totalCount = Db.Queryable<Student>().Count(); // SELECT COUNT(1) FROM Student int countWithCondition = Db.Queryable<Student>().Where(s => s.Age > 20).Count(); // 2. 判断是否存在符合条件的记录 - Any bool exists = Db.Queryable<Student>().Where(s => s.Name == "张三").Any(); // SELECT 1 FROM Student WHERE name='张三' LIMIT 1 // Any比 Count() > 0 更高效,因为找到一条就返回。 // 3. 聚合查询 - Max, Min, Sum, Avg int maxAge = Db.Queryable<Student>().Max(s => s.Age); double avgAge = Db.Queryable<Student>().Avg(s => s.Age); decimal totalScore = Db.Queryable<Score>().Where(s => s.StudentId == 1).Sum(s => s.Points); // 4. 分组聚合 - GroupBy var groupResult = Db.Queryable<Student>() .GroupBy(s => s.ClassId) .Select(s => new { ClassId = s.ClassId, Count = SqlFunc.AggregateCount(s.Id), AvgAge = SqlFunc.AggregateAvg(s.Age) }) .ToList(); // 生成的SQL类似于:SELECT class_id, COUNT(id), AVG(age) FROM Student GROUP BY class_id性能提示:
- 用
Any()替代Count() > 0:当你只关心“有没有”,而不关心“有多少”时,Any()是更好的选择。它的SQL通常是SELECT 1 FROM ... WHERE ... LIMIT 1,数据库找到第一条匹配记录就返回,效率更高。而Count()需要扫描所有匹配的行。 - 聚合查询的NULL处理:数据库里如果都是NULL,
Max、Min、Sum、Avg可能会返回NULL。C#中int、double等值类型不能为NULL,所以SqlSugar通常会返回默认值(如0)。如果你的业务逻辑需要区分“没有数据”和“总和为0”,可以考虑使用可空类型,或者先Any()判断一下。
4. 条件构建与分页查询实战
实际项目中,查询很少是简单的SELECT *,总是伴随着动态的条件和分页需求。
4.1 灵活构建动态查询条件
前端传过来的搜索条件五花八门,我们需要安全地将其组合到查询中。
错误示范(SQL注入风险!):
string name = Request.Query["name"]; // 假设用户输入了 `' OR '1'='1` var sql = $"SELECT * FROM Student WHERE Name = '{name}'"; // 永远不要这样写!正确姿势:使用Expression或ConditionalFilter
// 方法1:使用Expression动态拼接(最常用) public List<Student> SearchStudents(string name, int? minAge, int? maxAge) { var query = Db.Queryable<Student>(); if (!string.IsNullOrEmpty(name)) { query = query.Where(s => s.Name.Contains(name)); // Contains会被翻译为 LIKE '%{name}%' } if (minAge.HasValue) { query = query.Where(s => s.Age >= minAge.Value); } if (maxAge.HasValue) { query = query.Where(s => s.Age <= maxAge.Value); } return query.ToList(); } // SqlSugar会将Lambda表达式转换为参数化查询,有效防止SQL注入。 // 方法2:使用ConditionalFilter(适用于更复杂的动态逻辑) var query = Db.Queryable<Student>(); query.WhereIF(!string.IsNullOrEmpty(name), s => s.Name.Contains(name)) .WhereIF(minAge > 0, s => s.Age >= minAge); // `WhereIF`方法第一个参数是bool条件,为true时才应用后面的过滤条件。代码更紧凑。关于Contains、StartsWith、EndsWith:
s.Name.Contains(keyword)生成Name LIKE '%keyword%'s.Name.StartsWith(keyword)生成Name LIKE 'keyword%'s.Name.EndsWith(keyword)生成Name LIKE '%keyword'注意:前模糊(%keyword)和全模糊(%keyword%)查询会导致数据库索引失效,在大数据表上慎用。如果必须用,请考虑使用全文检索技术(如Elasticsearch)。
4.2 分页查询的正确实现与性能考量
分页是Web应用的标配。SqlSugar提供了非常便捷的分页方法,但用法不对,性能差十倍。
// 标准分页查询 int pageIndex = 1; // 当前页码,通常从1开始 int pageSize = 10; // 每页条数 RefAsync<int> totalCount = 0; // 用于接收总记录数的引用参数 List<Student> pageList = await Db.Queryable<Student>() .Where(s => s.Age > 10) .OrderBy(s => s.Id) // 分页必须排序!否则每次返回的顺序可能不一致。 .ToPageListAsync(pageIndex, pageSize, totalCount); Console.WriteLine($"当前页数据:{pageList.Count}条,总记录数:{totalCount.Value}条"); // 通常你需要将 pageList 和 totalCount 一起返回给前端,用于生成分页控件。分页查询的三大黄金法则:
- 必须排序 (
OrderBy):没有ORDER BY的分页查询,数据库每次返回的数据顺序可能都是随机的(取决于执行计划),这会导致用户看到的数据错乱、重复或丢失。这是分页最最基本的要求。 - 理解
ToPageList的参数:ToPageListAsync(pageIndex, pageSize, totalCount)。pageIndex: 页码,从1开始。pageSize: 每页大小。totalCount: 一个RefAsync<int>类型的输出参数。方法执行后,总记录数会填充到这里。这里有个坑:这个totalCount是本次查询条件对应的总记录数,SqlSugar会额外执行一条COUNT(*)的SQL来获取它。这意味着一次分页查询,实际上可能产生了两条SQL:一条COUNT(*),一条带OFFSET ... FETCH ...(或LIMIT ...)的分页查询。
- 警惕深度分页的性能问题:
OFFSET分页(即pageIndex很大时)在数据量大的情况下性能很差。例如OFFSET 10000 ROWS FETCH NEXT 10 ROWS ONLY,数据库需要先扫描并跳过前10000行,效率低下。- 优化方案:对于深度分页,考虑使用“游标分页”或“键集分页”。即记录上一页最后一条记录的ID(或排序字段值),下一页查询用
WHERE id > lastId ORDER BY id LIMIT 10。这需要业务逻辑和前端配合。
- 优化方案:对于深度分页,考虑使用“游标分页”或“键集分页”。即记录上一页最后一条记录的ID(或排序字段值),下一页查询用
ToPageList内部发生了什么?以SQL Server为例,当你调用ToPageListAsync(2, 10, ref total)时,SqlSugar大致会生成并执行以下SQL:
-- 1. 先查总数 SELECT COUNT(1) FROM Student WHERE Age > 10; -- 2. 再查分页数据 SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY Id) AS RowIndex, * FROM Student WHERE Age > 10 ) AS T WHERE RowIndex BETWEEN 11 AND 20; -- (pageIndex-1)*pageSize+1 到 pageIndex*pageSize了解这个,你就能明白为什么分页查询的成本相对较高。
5. 联表查询与导航属性填充
单表查询满足不了复杂业务,我们经常需要关联多张表。SqlSugar提供了两种主流的联表方式:Mapper和Includes。
5.1 使用Mapper进行手动映射联表查询
Mapper方式更接近原生SQL的思维,灵活性强,适合复杂的多表关联场景。
// 假设有Student和Class表,Student.ClassId 关联 Class.Id var list = Db.Queryable<Student>() .LeftJoin<Class>((s, c) => s.ClassId == c.Id) // 左连接 .Where((s, c) => s.Age > 18 && c.Name.Contains("实验班")) .Select((s, c) => new StudentViewModel // 自定义返回对象,避免循环引用和性能浪费 { StudentId = s.Id, StudentName = s.Name, ClassName = c.Name, Teacher = c.TeacherName }) .ToList(); // 如果需要将关联的Class对象填充到Student的导航属性里,可以使用.Mapper var listWithNav = Db.Queryable<Student>() .LeftJoin<Class>((s, c) => s.ClassId == c.Id) .Select((s, c) => s) // 主表选择Student .Mapper(s => s.Class, s => s.ClassId) // 关键!将关联的Class映射到Student.Class属性 .ToList(); // 此时,listWithNav中每个Student对象的Class属性都被填充了。Mapper方法详解:
Mapper<T, T2>(Func<T, T2> setAction, Func<T, object> whereField):这是最常用的重载。setAction: 指定如何设置导航属性,通常是s => s.Class。whereField: 指定用于关联的字段,通常是s => s.ClassId。SqlSugar内部会根据这个关联条件,将之前Join出来的Class数据,匹配并设置到对应的Student对象上。
- 优点:控制力强,可以处理非常复杂的多级嵌套关联。
- 缺点:代码稍显繁琐,需要手动指定映射关系。
5.2 使用Includes进行自动映射(一对一/一对多)
Includes是Mapper的语法糖,在简单的一对一、一对多关联中,写起来更简洁。
// 一对一映射 (Student -> Class) var student = Db.Queryable<Student>() .Includes(s => s.Class) // 自动根据Student.ClassId和Class.Id进行关联 .First(s => s.Id == 1); // 现在 student.Class 不为null // 一对多映射 (Class -> List<Student>) // 首先,在Class实体中定义导航属性 public class Class { [SugarColumn(IsPrimaryKey = true)] public int Id { get; set; } public string Name { get; set; } [Navigate(NavigateType.OneToMany, nameof(Student.ClassId))] // 一对多,关联字段是Student.ClassId public List<Student> Students { get; set; } } // 查询时使用Includes var classWithStudents = Db.Queryable<Class>() .Includes(c => c.Students) // 自动查询关联的所有Student .First(c => c.Id == 10); // 现在 classWithStudents.Students 包含了这个班级的所有学生列表Includes的工作原理与局限:Includes看起来很美好,但它背后可能执行了“N+1查询”。以上面的Includes(c => c.Students)为例:
- 先执行一条SQL查询Class:
SELECT * FROM Class WHERE id = 10。 - 再执行一条SQL查询关联的Student:
SELECT * FROM Student WHERE class_id IN (10)。 如果主查询返回了多个Class(比如ToList()),那么第二步的SQL就会变成WHERE class_id IN (10, 20, 30...)。这比在一条SQL里用JOIN要清晰,但有时效率不如手动优化过的JOIN。
- 局限:
Includes主要适用于一对一、一对多这种明确的导航关系。对于多对多,或者关联条件非常复杂的情况,还是得用Mapper。
避坑指南:无论是
Mapper还是Includes,都要警惕循环引用和数据膨胀。
- 循环引用:例如
Student有一个Class属性,Class又有一个Students属性(List<Student>)。如果你用Includes同时加载了两边,序列化成JSON时会陷入死循环。解决方案是使用[JsonIgnore]特性忽略其中一个导航属性,或者使用Select返回DTO(数据传输对象),只选择需要的字段。- 数据膨胀:联表查询时,如果主表的一条记录对应子表的多条记录,用
JOIN方式会导致主表数据重复。Mapper和Includes在内部做了处理,会帮你组装成对象树,避免了这个问题。
6. 常见报错排查与性能优化锦囊
即使理解了所有概念,在实际编码中依然会遇到各种报错和性能问题。这里我整理了几个最常见的问题和解决方法。
6.1 高频报错排查速查表
| 报错信息/现象 | 可能原因 | 解决方案 |
|---|---|---|
SqlSugar.SqlSugarException: Connection open error . | 1. 连接字符串错误。 2. 数据库服务未启动。 3. 网络不通或防火墙拦截。 4. 连接池耗尽(长时间运行后)。 | 1. 仔细检查连接字符串的服务器、数据库名、用户名、密码。 2. 确认SQL Server/MySQL等服务已启动。 3. 使用 telnet或数据库客户端测试连通性。4. 检查代码中是否有连接未关闭(确保 IsAutoCloseConnection=true),或在数据库层面调整连接池设置。 |
SqlSugar.SqlSugarException: The table ‘XXX’ doesn‘t exist. | 1. 表名写错,或数据库不存在此表。 2. 实体类未用 [SugarTable]指定表名,且类名与表名不一致。3. 数据库连接到了错误的库。 | 1. 在数据库管理工具中确认表名。 2. 在实体类上添加 [SugarTable(“实际表名”)]。3. 检查连接字符串中的 Database或Initial Catalog。 |
System.InvalidOperationException: Sequence contains no elements | 调用First()或Single()时,查询结果为空。 | 使用FirstOrDefault()或SingleOrDefault()替代,它们会在无结果时返回null。或者在调用前先用Any()判断。 |
System.InvalidOperationException: Sequence contains more than one element | 调用Single()或SingleOrDefault()时,查询结果多于一条。 | 确认你的查询条件是否足以唯一确定一条记录(如使用主键)。如果不是,改用First()或FirstOrDefault()。 |
导航属性为null | 定义了[Navigate]属性,但查询时没有使用Includes或Mapper加载。 | 查询时必须显式调用.Includes(s => s.Class)或.Mapper来加载导航属性。 |
| 更新/删除时提示“找不到主键” | 实体类的主键属性未用[SugarColumn(IsPrimaryKey=true)]标记。 | 检查实体类,确保主键属性正确标记。自增主键通常还需标记IsIdentity=true。 |
6.2 性能优化核心要点
只查询需要的字段(
Select):这是最重要的优化原则。避免使用Queryable<T>().ToList()这种查全表所有字段的方式。// 不好 var list = Db.Queryable<Order>().ToList(); // SELECT * FROM Order // 好 var list = Db.Queryable<Order>().Select(o => new { o.Id, o.OrderNo, o.CreateTime }).ToList(); // 或者使用DTO var list = Db.Queryable<Order>().Select(o => new OrderBriefDto { Id = o.Id, No = o.OrderNo }).ToList();网络传输的数据量越少,速度越快,内存占用也越小。
善用索引,优化
Where条件:- 确保查询条件中的字段(尤其是等值查询和范围查询的字段)建立了数据库索引。
- 避免在索引列上使用函数或计算,如
WHERE YEAR(CreateTime) = 2023,这会导致索引失效。应改为WHERE CreateTime >= '2023-01-01' AND CreateTime < '2024-01-01'。 - 谨慎使用
LIKE '%keyword%'全模糊查询,如前所述,它会让索引失效。
异步查询(
Async):在Web应用中,务必使用ToListAsync(),FirstAsync(),CountAsync()等异步方法。这能释放线程池线程,提高应用的并发处理能力。分页查询的优化:
- 如前所述,避免深度分页。考虑使用“上一页最后一条ID”的方式进行分页。
- 如果UI允许,可以提供“仅加载更多”的模式,而不是传统的页码跳转。
监控生成的SQL:SqlSugar提供了方便的SQL输出功能,在开发阶段开启它,能帮你发现潜在的性能问题。
Db.Aop.OnLogExecuting = (sql, pars) => { Console.WriteLine(sql); // 输出SQL语句 // 也可以输出参数 Console.WriteLine(string.Join(",", pars?.Select(it => it.ParameterName + ":" + it.Value))); };观察生成的SQL是否符合预期,有没有多余的
JOIN或者SELECT *。批量操作:对于大量数据的插入、更新,使用SqlSugar提供的
InsertRange、UpdateRange等方法,或者使用Storageable进行大数据处理,这比在循环中执行单条SQL语句要高效得多。
基础查询是SqlSugar的基石,掌握这些内容,你就能应对日常开发中80%的数据库查询场景。记住,写出正确的查询只是第一步,写出高效的查询才是我们追求的目标。多观察生成的SQL,多思考业务场景,你的代码质量会越来越高。在后续的分享中,我们会继续深入SqlSugar的增删改、事务、以及更高级的查询特性。