SQLsugar自连接导航查询中select使用子查询没有数据

在 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) 会改变主表的别名,导致子查询中的条件失效:

  1. 导航查询(Include)的 SQL 生成逻辑
    当使用 Includes(u => u.children) 时,SqlSugar 会自动生成自连接查询,主表 Project 会被赋予别名(如 u1),而子级 children 会被赋予另一个别名(如 u2)。
  2. 子查询中的别名冲突
    子查询中使用 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
    });

注意:主表别名可能随查询复杂度变化(如 u1u2),需通过日志确认后调整,不推荐在生产环境硬编码。

方案 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)导致子查询无数据的核心原因是 主表别名被改写,导致子查询条件关联失效。推荐采用 拆分查询 的方式,避免导航属性与复杂子查询在同一语句中冲突。若必须同时使用,需通过日志确认主表别名并调整子查询条件。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注