EF Core为何同键多次LEFT JOIN同一张testruntestgrp表
重复LEFT JOIN的根本原因
这是EF Core 3.1.x版本的查询翻译机制缺陷导致的,核心是该版本的翻译器不会自动合并同一导航属性在查询不同位置的引用,你代码里对Testruntestgrp导航属性的多次独立访问,被翻译器识别成了独立的关联需求:
- 第一个
LEFT JOIN fit.testruntestgrp(别名t0)来自你显式书写的Include(x => x.Testruntestgrp)语句,作用是加载关联的测试组数据,填充Testrunconfig实体上的导航属性。 - 第二个
LEFT JOIN fit.testruntestgrp(别名t1)来自你在投影阶段调用的本地自定义方法GetFinalResult(x):EF Core 3.1无法翻译自定义C#方法内部的逻辑,识别到方法内部访问了Testruntestgrp导航属性做Any判断,就额外生成了一次关联拉取数据,等数据加载到内存后再执行你写的本地方法逻辑。 - 你在Where条件里写的
x.Testruntestgrp.Select(y => y.Testname).Contains(testName)被翻译成了独立的关联子查询(别名t2),没有走JOIN逻辑,所以没生成第三个LEFT JOIN。
这种重复JOIN会导致查询结果出现笛卡尔积,比如一个Testrunconfig关联N条Testruntestgrp记录,两次JOIN会返回N*N条冗余数据,平白增加数据库的查询压力和内存占用。
修复方案
你可以选任意一种方案解决:
- 方案1:把
GetFinalResult的逻辑直接内联到Select投影中,不要调用外部本地方法。EF Core可以直接翻译内联的判断逻辑,不会生成多余JOIN,甚至可以去掉冗余的Include语句(投影中用到什么字段EF Core会自动加载),修改后的投影代码如下:
.Select(x => new TestExecutionHelper() { TestrunConfig = x, TestSelected = testSelected, SelectedTestName = testName, FinalResult = x.Finished == null ? "NOT FINISHED" : x.Testruntestgrp.Any(t => t.Status != "OK") ? "ERROR" : "OK" })
- 方案2:拆分查询为两段,先把需要的实体数据通过带Include的查询加载到内存,再在内存中执行投影和本地方法调用,避免EF Core翻译阶段误生成多余JOIN,写法参考:
// 先查数据库,这一步只会生成一次Testruntestgrp的LEFT JOIN var configs = _fitDbContext.Testrunconfig .Include(x => x.Testruntestgrp) .Include(x => x.Product) .Where(x => x.Configfilename == configuration) .Where(x => x.Testruntestgrp.Any(y => y.Testname == testName)) // 直接用Any比Select+Contains翻译效率更高 .OrderByDescending(x => x.Id) .ToList(); // 内存中做投影,调用本地方法不会再生成SQL var test = configs.Select(x => new TestExecutionHelper() { TestrunConfig = x, TestSelected = testSelected, SelectedTestName = testName, FinalResult = GetFinalResult(x) }).ToList();
- 方案3:升级EF Core以及对应的Npgsql驱动到5.0以上版本,新版本重构了查询翻译层,会自动复用同一导航属性的关联,不会再生成这种无意义的重复JOIN。
内容的提问来源于stack exchange,提问作者Aleks Vujic
相关产品推荐
相关产品推荐

