在 SqlSugar 中,導航查詢(Include)與子(zǐ)查詢同時(shí)使用(yòng)時(shí)出現(xi✔€™↓àn)無數(shù)據的(de)情況,通(tōng)常與 查詢别名沖突 或 導航查詢結構被導航加載幹擾 有(yǒu)關。以下(xià)通(tōngβ$λ)過具體(tǐ)測試案例分(fēn)析原因并提供解決方案。
測試場(chǎng)景複現(xiàn)
假設我們有(yǒu)兩個(gè)實體(tǐ)類,模拟項目(Project)和(hé)系統文(wén)件(jiàn)(SysFile)的(de)關系,其中 Project 包含自(zì)引用(yòng)的(de)子(zǐ↓✘↓&)級導航屬性 children:
csharp
// 項目實體(tǐ)
public class Projec♥εt
{
public int Id { get; set; }¥∞ ¥
public string title { get; •¥λset; }
public string hetong { get; se≥ ↔t; } // 關聯合同文(wén)件(jiàn)URL,關聯SysFile.Urσ§₽&l
public int? pid { get; set; } // 父級λ£項目ID(自(zì)引用(yòng))
>π // 導航屬性:子(zǐ)級項目(自(zì)引用(yòn>≤g))
[Navigate(NavigateType.On÷λγeToMany, nameof(pid))]
public List&®♦lt;Project> children { get <; set; }
}
// 系統文(wén)件(jiàn)實體(tǐ)
pub§→lic class SysFile
{
public int Id { get; set;αλ♦∞ }
public string FileN¶ame { get; set; }
public string Url { g→"≠αet; set; } // 與Project.hetong關聯
}
≠>
// 輸出DTO
public class ProjectOutpuα≤λ©t
{
public int Id { get; set; }
™♦÷β public string title { get; set; }
π public List<elfile> hetongAttach★λ↔ment { get; set; } // 子(zǐ)查詢結果
₹™ public List<Project> children { π≥get; set; } // 導航屬性
}
public class elfileφ®
{
public string name { •♦get; set; }
public string url { get; set; }<♠
}
問(wèn)題查詢代碼(子(zǐ)查詢無數(shù)據)
csharp
// 有(yǒu)問(wèn)題的(de)查詢:同時(shíφ)使用(yòng)Include(children)和(hé)子(zǐ)查詢
var quer↓∑δy = db.Queryable<Project>()
§↔∞§ .Includes(u => u.children)&✔ // 加載子(zǐ)級導航屬性
.Select(u => ne®'→w ProjectOutput
{
Id = u.Idλδβ,
title = u.title,
¥☆ // 子(zǐ)查詢:通(tōng)過hetong關聯SysFile≥↓✘
hetongAttachment = SqlFunc.Subqueryaφ÷ble<SysFile>()
.Where(f<€¥ => f.Url == u.hetong)
.Select(f =¥✔π¶> new elfile { name = f.FileName, url γ•= f.Url })
.ToList(ε≈),
children = u.ch≠πildren // 導航屬性
});
var result = await ↕↓query.ToListAsync();
// 現(xiàn)象:hetongAttachmen ∏βt始終為(wèi)空(kōng)(即使有(yǒu)匹配數(shù)據)
問(wèn)題原因分(fēn)析
通(tōng)過輸出 SQL 日(rì)志(zhì)(配置方式見(jiàn)下(x σià)文(wén)),發現(xiàn) Include(children) 會(huì)改變主表的(de)别名,導緻子(zǐ)查詢中的(de)條件(jiàn)失效:
- 導航查詢(Include)的(de) SQL 生(shēng☆γ)成邏輯:
當使用(yòng)Includes(u => u.children)時(shí),SqlSugar 會(huì)自(zì)動生(shēng)成±₹自(zì)連接查詢,主表Project會(huì)被賦予别名(如(rú)u1),而子(zǐ)級children會(huì)被賦予另一(yī)個(gè)别名(如(rú)u2)。 - 子(zǐ)查詢中的(de)别名沖突:
子(zǐ)查詢中使用(yòng)u.hetong時(shí),SqlSugar 會(huì)默認引用(yòng)主表原始别名♦ε♣∏(如(rú)u),但(dàn)實際主表已被重命名為(wèi)u1,導緻條件(jiàn)f.Url == u.hetong實際被解析為(wèi)f.Url == u.hetong(而非u1.hetong),條件(jiàn)不(bù)匹配,子(zǐ)查詢無結果。
解決方案
方案 1:拆分(fēn)查詢(推薦)
将 “導航屬性加載” 和(hé) “子(zǐ)查詢” 拆分(fēn)為(wè↓₹i)兩步,避免别名沖突:
csharp
// 第一(yī)步:查詢主數(shù)據+子(zǐ)查詢(不(bù)加載c♣✔λ←hildren)
var query = db.Queryable&÷λlt;Project>()
.Select(u => new Pr♦∞δεojectOutput
{
Id = u.Id,∏™ €
title = u.title,
hetongAttachγ§≈∞ment = SqlFunc.Subqueryable<★;SysFile>()
.Where(±φα>f => f.Url == u.hetong)
™φ .Select(f => new elfile { nσ"×ame = f.FileName, url = f.Url })
γ↑δ .ToList()
});
var result = await↕•✔ query.ToListAsync();
// 第二↑®步:單獨加載children導航屬性
if (result.Any())
{
va✔r parentIds = result.Select(r => §®r.Id).ToList();
// 查詢所有(yǒu)子(zǐ)級項目
va₽←r children = await db.Queryable<Proj₽Ω¥>ect>()
.Where(c => parentIds♣★±∞.Contains(c.pid ?? 0))
σ¶ .ToListAsync();
// 手動關聯子(zǐ)級
fore☆↓☆ach (var item in result)
{
↓₩β item.children = children.Whβ↓ ere(c => c.pid == item.Id)≥.ToList();
}
}
方案 2:強制(zhì)子(zǐ)查詢引用(yòng)主表實際别名
通(tōng)過 SQL 日(rì)志(zhì)獲取主♠§✔表在 Include 後的(de)實際别名(如(rú) u1),使用(yòng) SqlFunc.GetSelfColumn 強制(zhì)子(zǐ)查詢引用(yòng)正确别名:
csharp
var query = db.Queryable<Project&g♣↑↔t;()
.Includes(u => u.children)
↕↑♣ .Select(u => new ProjectOutput λ€
{
Id = u.Id,
✘ title = u.title,
// 關鍵:使用(yòng)∞÷α主表實際别名(從(cóng)SQL日(rì)志(zhì)獲取,如(rú)u1)
™÷♠ hetongAttachment = SqlFunc.Subque$∞ryable<SysFile>()
.Where(fγ => f.Url == SqlFunc.GetSelfColumn(&→αφ₽quot;u1.hetong"))
.←↕ΩSelect(f => new elfile { name = → ©♣f.FileName, url = f.Url })
&Ω .ToList(),
children = u.childδ§ren
});
注意:主表别名可(kě)能(néng)随查詢複雜(zá)度變化(huà)(如(rú) u1、u2),需通(tōng)過日(rì)志(zhì)确認後調整,不(bù)推薦在生(shēng)産± ∏環境硬編碼。
方案 3:禁用(yòng)導航查詢的(de)自(zì)動别名(謹慎使用(yòng))
通(tōng)過配置 Aop 禁用(yòng)導航查詢的(de)别名生(shēng)成(可(kě)能(né' ng)影(yǐng)響其他(tā)邏輯,需測試):
csharp
// 初始化(huà)SqlSugar時(shí)配置
db. βAop.ConfigureJoinTable = (joinItems) =>
{
↑©≥© // 強制(zhì)主表别名固定為(wèi)"u&q≠← uot;
foreach (var item in joinItems)
★πσδ{
if (item.IsMainTable)
{
<™↔× item.Alias = "u"≥; // 固定主表别名為(wèi)u
}
}
};
關鍵排查工(gōng)具:輸出 SQL 日(rì)志(zhì)
配置 SqlSugar 日(rì)志(zhì),查看(kàn)生(sh₹✘ēng)成的(de) SQL 語句,确認主表别名和(hé)子(zǐ)查詢條件(jiàn):
csharp
// 初始化(huà)SqlSugar時(shí)添加日(rì)志(zhì)配置
db.↕≈Aop.OnLogExecuting = (sql, pars) =>
{
C↓∏onsole.WriteLine("生(shēng)成的(de)S$QL:" + sql);
Console.WriteLine("參數(₽φshù):" + string.Join("↕σ>,", pars.Select(p => $±∞&♣"{p.ParameterName}={p.Value}"))>Ω);
};

對(duì)比 “不(bù)加 Include” 和(hé) “加 Include” 的(de) SQL,重點觀察:
- 主表的(de)别名是(shì)否變化(huà)(如(rú)
u→u1)。 - 子(zǐ)查詢中的(de)
u.hetong是(shì)否正确關聯到(dào)主表别名。
總結
導航查詢(Include)導緻子(zǐ)查詢無數(shù)據的(de)核心原因是(shì)&nb☆♠ sp;主表别名被改寫,導緻子(zǐ)查詢條件(jiàn)關聯失效。推薦采用(yòng) 拆分(fēn)查詢 的(de)方式,避免導航屬性與複雜(zá)子(zǐφ♦₩)查詢在同一(yī)語句中沖突。若必須同時(shí)使用(yòng),需通¥(tōng)過日(rì)志(zhì)确認主表别名并調整εε子(zǐ)查詢條件(jiàn)。