在 SqlSugar 中,导航查询(Include
)与子查询同时使用时出现无数据的情况,通常与 查询别名冲突 或 导航查询结构被导航加载干扰 有关。以下通过具体测试案例分析原因并提供解决方案。
测试场景复现
假设我们有两个实体类,模拟项目(Project
)和系统文件(SysFile
)的关系,其中 Project
包含自引用的子级导航属性 children
:
csharp
// 项目实体 public class Project { public int Id { get; set; } public string title { get; set; } public string hetong { get; set; } // 关联合同文件URL,关联SysFile.Url public int? pid { get; set; } // 父级项目ID(自引用) // 导航属性:子级项目(自引用) [Navigate(NavigateType.OneToMany, nameof(pid))] public List<Project> children { get; set; } } // 系统文件实体 public class SysFile { public int Id { get; set; } public string FileName { get; set; } public string Url { get; set; } // 与Project.hetong关联 } // 输出DTO public class ProjectOutput { public int Id { get; set; } public string title { get; set; } public List<elfile> hetongAttachment { get; set; } // 子查询结果 public List<Project> children { get; set; } // 导航属性 } public class elfile { public string name { get; set; } public string url { get; set; } }
问题查询代码(子查询无数据)
csharp
// 有问题的查询:同时使用Include(children)和子查询 var query = db.Queryable<Project>() .Includes(u => u.children) // 加载子级导航属性 .Select(u => new ProjectOutput { Id = u.Id, title = u.title, // 子查询:通过hetong关联SysFile hetongAttachment = SqlFunc.Subqueryable<SysFile>() .Where(f => f.Url == u.hetong) .Select(f => new elfile { name = f.FileName, url = f.Url }) .ToList(), children = u.children // 导航属性 }); var result = await query.ToListAsync(); // 现象:hetongAttachment始终为空(即使有匹配数据)
问题原因分析
通过输出 SQL 日志(配置方式见下文),发现 Include(children)
会改变主表的别名,导致子查询中的条件失效:
- 导航查询(Include)的 SQL 生成逻辑:
当使用Includes(u => u.children)
时,SqlSugar 会自动生成自连接查询,主表Project
会被赋予别名(如u1
),而子级children
会被赋予另一个别名(如u2
)。 - 子查询中的别名冲突:
子查询中使用u.hetong
时,SqlSugar 会默认引用主表原始别名(如u
),但实际主表已被重命名为u1
,导致条件f.Url == u.hetong
实际被解析为f.Url == u.hetong
(而非u1.hetong
),条件不匹配,子查询无结果。
解决方案
方案 1:拆分查询(推荐)
将 “导航属性加载” 和 “子查询” 拆分为两步,避免别名冲突:
csharp
// 第一步:查询主数据+子查询(不加载children) var query = db.Queryable<Project>() .Select(u => new ProjectOutput { Id = u.Id, title = u.title, hetongAttachment = SqlFunc.Subqueryable<SysFile>() .Where(f => f.Url == u.hetong) .Select(f => new elfile { name = f.FileName, url = f.Url }) .ToList() }); var result = await query.ToListAsync(); // 第二步:单独加载children导航属性 if (result.Any()) { var parentIds = result.Select(r => r.Id).ToList(); // 查询所有子级项目 var children = await db.Queryable<Project>() .Where(c => parentIds.Contains(c.pid ?? 0)) .ToListAsync(); // 手动关联子级 foreach (var item in result) { item.children = children.Where(c => c.pid == item.Id).ToList(); } }
方案 2:强制子查询引用主表实际别名
通过 SQL 日志获取主表在 Include
后的实际别名(如 u1
),使用 SqlFunc.GetSelfColumn
强制子查询引用正确别名:
csharp
var query = db.Queryable<Project>() .Includes(u => u.children) .Select(u => new ProjectOutput { Id = u.Id, title = u.title, // 关键:使用主表实际别名(从SQL日志获取,如u1) hetongAttachment = SqlFunc.Subqueryable<SysFile>() .Where(f => f.Url == SqlFunc.GetSelfColumn("u1.hetong")) .Select(f => new elfile { name = f.FileName, url = f.Url }) .ToList(), children = u.children });
注意:主表别名可能随查询复杂度变化(如 u1
、u2
),需通过日志确认后调整,不推荐在生产环境硬编码。
方案 3:禁用导航查询的自动别名(谨慎使用)
通过配置 Aop
禁用导航查询的别名生成(可能影响其他逻辑,需测试):
csharp
// 初始化SqlSugar时配置 db.Aop.ConfigureJoinTable = (joinItems) => { // 强制主表别名固定为"u" foreach (var item in joinItems) { if (item.IsMainTable) { item.Alias = "u"; // 固定主表别名为u } } };
关键排查工具:输出 SQL 日志
配置 SqlSugar 日志,查看生成的 SQL 语句,确认主表别名和子查询条件:
csharp
// 初始化SqlSugar时添加日志配置 db.Aop.OnLogExecuting = (sql, pars) => { Console.WriteLine("生成的SQL:" + sql); Console.WriteLine("参数:" + string.Join(",", pars.Select(p => $"{p.ParameterName}={p.Value}"))); };

对比 “不加 Include
” 和 “加 Include
” 的 SQL,重点观察:
- 主表的别名是否变化(如
u
→u1
)。 - 子查询中的
u.hetong
是否正确关联到主表别名。
总结
导航查询(Include
)导致子查询无数据的核心原因是 主表别名被改写,导致子查询条件关联失效。推荐采用 拆分查询 的方式,避免导航属性与复杂子查询在同一语句中冲突。若必须同时使用,需通过日志确认主表别名并调整子查询条件。