1. SqlSugar多表查询实战解析
在数据库操作中,单表查询是最基础的操作,但实际业务场景往往需要关联多个表获取数据。SqlSugar作为.NET平台优秀的ORM框架,提供了强大的多表查询支持。下面我将结合多年项目经验,详细解析SqlSugar的多表查询实现方式。
1.1 多表连接基础实现
SqlSugar提供了多种多表查询方法,最基础的是通过Queryable<T, T2>方法:
csharp复制var query = context.Client.Queryable<Person, Employee>((pr, em) => new object[]
{
JoinType.Left,
em.PersonId == pr.Id
});
这段代码实现了Person和Employee表的左连接查询。其中:
- JoinType.Left指定连接类型为左连接
- em.PersonId == pr.Id定义了连接条件
- 返回的是ISugarQueryable接口,可以继续链式调用其他查询方法
实际开发中,我建议将连接条件提取为常量或变量,避免在多个地方重复编写相同的连接条件,这样既减少出错概率,也便于后期维护。
1.2 多表查询的四种实现方式
SqlSugar提供了四种多表查询方式,各有适用场景:
方式一:基础连接查询
csharp复制var query = context.Client.Queryable<Person, Employee>((pr, em) => new object[]
{
JoinType.Left,
em.PersonId == pr.Id
});
特点:
- 语法简洁
- 适合简单的两表连接
- 需要显式指定连接类型
方式二:基于已有查询的连接
csharp复制var personQuery = context.Client.Queryable<Person>();
var employeeQuery = context.Client.Queryable<Employee>();
var query = context.Client.Queryable(personQuery, employeeQuery,
(pr, em) => pr.Id == em.PersonId);
特点:
- 可以对已有查询进行连接
- 默认使用内连接
- 适合需要对单表先进行筛选再连接的场景
方式三:指定连接类型的查询
csharp复制var query = context.Client.Queryable(
context.Client.Queryable<Person>(),
context.Client.Queryable<Employee>(),
JoinType.Left,
(pr, em) => pr.Id == em.PersonId);
特点:
- 可以自由指定连接类型
- 结合了前两种方式的优点
- 适合复杂查询场景
方式四:简化版连接查询
csharp复制var query = context.Client.Queryable<Person, Employee>(
(pr, em) => pr.Id == em.PersonId);
特点:
- 语法最简洁
- 默认使用内连接
- 适合快速开发场景
1.3 多表查询的性能优化
在实际项目中,多表查询性能是需要重点考虑的因素。以下是我总结的几个优化技巧:
- 只查询需要的字段:避免使用SELECT *,明确指定需要返回的字段
csharp复制query.Select((pr, em) => new {
pr.Name,
em.Department
})
-
合理使用索引:确保连接条件字段和常用查询条件字段建立了索引
-
控制连接表数量:尽量避免超过3个表的连接,必要时考虑拆分为多个查询
-
使用延迟加载:对于不立即需要的数据,可以使用导航属性延迟加载
-
分页处理:大数据量查询一定要实现分页
csharp复制query.ToPageList(pageIndex, pageSize);
2. SqlSugar查询函数深度解析
SqlSugar内置了大量实用的查询函数,可以极大简化我们的开发工作。这些函数最终都会转换为对应的SQL函数执行。
2.1 常用聚合函数
聚合函数是数据分析中最常用的功能:
csharp复制// 平均值
var avgAge = context.Client.Queryable<Person>()
.Select(p => SqlFunc.AggregateAvg(p.Age))
.ToScalar();
// 计数
var count = context.Client.Queryable<Person>()
.Select(p => SqlFunc.AggregateCount(p.Id))
.ToScalar();
// 最大值
var maxAge = context.Client.Queryable<Person>()
.Select(p => SqlFunc.AggregateMax(p.Age))
.ToScalar();
在实际项目中,我建议将常用的聚合查询封装为仓储方法,避免在业务代码中直接使用SqlFunc,这样可以使代码更加整洁。
2.2 字符串处理函数
字符串处理是业务开发中的常见需求:
csharp复制// 字符串连接
var fullName = context.Client.Queryable<Person>()
.Select(p => SqlFunc.MergeString(p.FirstName, " ", p.LastName))
.First();
// 子字符串
var shortName = context.Client.Queryable<Person>()
.Select(p => SqlFunc.Substring(p.Name, 0, 10))
.First();
// 转换大小写
var upperName = context.Client.Queryable<Person>()
.Select(p => SqlFunc.ToUpper(p.Name))
.First();
2.3 日期时间函数
日期时间处理是业务系统的重要功能:
csharp复制// 日期加减
var nextWeek = context.Client.Queryable<Order>()
.Select(o => SqlFunc.DateAdd(o.CreateTime, 7, DateType.Day))
.First();
// 日期比较
var isSameDay = context.Client.Queryable<Order>()
.Where(o => SqlFunc.DateIsSame(o.CreateTime, DateTime.Now, DateType.Day))
.Any();
// 获取日期部分
var year = context.Client.Queryable<Order>()
.Select(o => SqlFunc.DateValue(o.CreateTime, DateType.Year))
.First();
2.4 条件判断函数
复杂的业务逻辑经常需要条件判断:
csharp复制// IIF函数
var statusText = context.Client.Queryable<Order>()
.Select(o => SqlFunc.IIF(o.Status == 1, "已支付", "未支付"))
.First();
// CASE WHEN
var levelText = context.Client.Queryable<Customer>()
.Select(c => SqlFunc.Case()
.When(c.Score > 1000).Then("VIP")
.When(c.Score > 500).Then("高级")
.Else("普通")
.End())
.First();
3. 动态查询构建技巧
在实际业务开发中,查询条件往往需要根据用户输入动态构建。SqlSugar提供了多种实现动态查询的方式。
3.1 WhereIF条件判断
WhereIF是最简单的动态查询方式:
csharp复制var query = context.Client.Queryable<Person>();
if (!string.IsNullOrEmpty(name))
{
query = query.WhereIF(true, p => p.Name.Contains(name));
}
if (minAge.HasValue)
{
query = query.WhereIF(true, p => p.Age >= minAge.Value);
}
特点:
- 语法简单直观
- 适合条件较少的情况
- 条件间是AND关系
3.2 ConditionalModel动态构建
对于更复杂的动态查询,可以使用ConditionalModel:
csharp复制var conditions = new List<IConditionalModel>();
if (!string.IsNullOrEmpty(name))
{
conditions.Add(new ConditionalModel
{
FieldName = "Name",
ConditionalType = ConditionalType.Like,
FieldValue = name
});
}
if (minAge.HasValue && maxAge.HasValue)
{
conditions.Add(new ConditionalModel
{
FieldName = "Age",
ConditionalType = ConditionalType.Between,
FieldValue = $"{minAge},{maxAge}"
});
}
var result = context.Client.Queryable<Person>()
.Where(conditions)
.ToList();
3.3 表达式树动态构建
对于需要复杂逻辑的动态查询,可以使用表达式树:
csharp复制Expression<Func<Person, bool>> expr = p => true;
if (!string.IsNullOrEmpty(name))
{
expr = expr.And(p => p.Name.Contains(name));
}
if (minAge.HasValue)
{
expr = expr.And(p => p.Age >= minAge.Value);
}
var result = context.Client.Queryable<Person>()
.Where(expr)
.ToList();
在实际项目中,我建议将复杂的动态查询逻辑封装到专门的查询构建器中,避免业务代码中直接处理表达式树,这样可以提高代码的可维护性。
4. SqlSugar高级特性实战
除了基本的CRUD操作,SqlSugar还提供了一些高级特性,可以极大提升开发效率。
4.1 批量操作优化
SqlSugar的批量操作性能非常出色:
csharp复制// 批量插入
var insertObjs = new List<Person>();
context.Client.Insertable(insertObjs).ExecuteCommand();
// 批量更新
var updateObjs = new List<Person>();
context.Client.Updateable(updateObjs).ExecuteCommand();
// 批量删除
var ids = new List<int> {1, 2, 3};
context.Client.Deleteable<Person>().Where(p => ids.Contains(p.Id)).ExecuteCommand();
性能优化建议:
- 合理设置批量大小(默认1000)
- 大批量操作考虑分批次执行
- 使用事务保证数据一致性
4.2 事务处理最佳实践
SqlSugar提供了灵活的事务支持:
csharp复制try
{
context.Client.BeginTran();
// 业务操作1
context.Client.Insertable(person1).ExecuteCommand();
// 业务操作2
context.Client.Updateable(person2).ExecuteCommand();
context.Client.CommitTran();
}
catch (Exception ex)
{
context.Client.RollbackTran();
throw;
}
事务使用建议:
- 事务范围尽量小
- 避免在事务中执行耗时操作
- 考虑使用异步事务提升并发性能
4.3 执行原生SQL
虽然ORM很方便,但有时我们需要执行原生SQL:
csharp复制// 查询
var list = context.Client.Ado.SqlQuery<Person>("SELECT * FROM Person WHERE Age > @Age",
new { Age = 18 });
// 执行
var count = context.Client.Ado.ExecuteCommand(
"UPDATE Person SET Name = @Name WHERE Id = @Id",
new { Name = "NewName", Id = 1 });
// 存储过程
var result = context.Client.Ado.UseStoredProcedure()
.GetDataTable("sp_GetPerson", new { Id = 1 });
在实际项目中,我建议将原生SQL语句集中管理,可以使用资源文件或专门的SQL管理类,这样便于维护和SQL优化。
5. 性能优化与疑难解答
5.1 查询性能优化
- 索引优化:确保常用查询条件字段建立了索引
- 分页查询:大数据量一定要使用分页
- 延迟加载:对于关联数据,考虑使用延迟加载
- 缓存策略:合理使用二级缓存减少数据库压力
5.2 常见问题解决
问题1:导航属性不加载
解决方案:
csharp复制// 使用Include方法显式加载
var person = context.Client.Queryable<Person>()
.Includes(p => p.Employees)
.First();
问题2:批量操作超时
解决方案:
csharp复制// 设置命令超时时间
context.Client.Ado.CommandTimeOut = 600;
// 分批次执行
context.Client.Insertable(list).SplitTable().ExecuteCommand();
问题3:并发冲突
解决方案:
csharp复制// 使用乐观锁
[SugarColumn(IsEnableUpdateVersionValidation = true)]
public long Version { get; set; }
6. 实际项目经验分享
在多年的项目实践中,我总结了以下SqlSugar使用心得:
-
分层架构:将数据访问层与业务逻辑层分离,使用仓储模式封装SqlSugar操作
-
代码规范:
- 统一命名查询方法(如GetXxx、FindXxx)
- 使用DTO隔离实体类和返回结果
- 重要操作添加日志记录
-
性能监控:
- 记录慢查询
- 监控高频SQL
- 定期优化数据访问代码
-
团队协作:
- 制定统一的ORM使用规范
- 代码审查时重点关注数据访问逻辑
- 共享性能优化经验
-
测试策略:
- 单元测试覆盖核心数据访问逻辑
- 集成测试验证复杂查询
- 性能测试评估大数据量场景
在实际开发中,我发现合理使用SqlSugar的导航属性和延迟加载功能可以显著简化代码,但需要注意N+1查询问题。对于复杂报表查询,有时原生SQL可能是更好的选择。