EF Core 2.0.2对子查询生成多条SQL,疑为Bug引发性能问题求助
我太懂你这种糟心的感觉了——明明是逻辑上很简单的一条查询,EF Core却拆成多条SQL执行,性能直接拉胯。咱们先捋清楚问题出在哪,再一步步搞定它。
问题根源
你用let子句搭配First()的时候,EF Core的查询转换逻辑有时候会“转不过弯”,把这个子查询拆成N+1查询:先查所有Tasks,再给每条Task单独发一次查询去取TaskStatus,而不是把它合并成你期望的嵌套子查询。这在数据量大的时候,性能问题会特别突出。
解决方案
咱们调整下LINQ的写法,给EF Core明确的“信号”,让它生成单条SQL查询,这里有几种靠谱的方式:
方式1:把子查询直接放到投影里
把原来let子句的逻辑直接挪到匿名类的属性定义里,这种写法EF Core更容易识别成嵌套子查询:
var query = (from task in db.Tasks select new { task.Id, LastStatus = task.TaskStatus .Where(x => !x.Deleted) .OrderByDescending(x => x.CreatedDate) .Select(x => x.Status.Name) .FirstOrDefault() // 用FirstOrDefault避免无匹配数据时抛出异常,也可以继续用First() }).ToList();
方式2:用方法链写法优化
如果你的导航属性(TaskStatus和Status)配置正确,用方法链的写法也能引导EF Core生成正确的单条SQL:
var query = db.Tasks.Select(task => new { task.Id, LastStatus = task.TaskStatus .Where(ts => !ts.Deleted) .OrderByDescending(ts => ts.CreatedDate) .Select(ts => ts.Status.Name) .First() }).ToList();
方式3:升级EF Core版本(额外建议)
如果用了上面的写法还是没效果,建议检查下你的EF Core版本——旧版本的查询转换能力确实弱一些,升级到最新的稳定版,很多这类自动优化的问题都会被修复。
验证生成的SQL
你可以开启EF Core的日志功能,确认生成的SQL是不是符合预期:
// 以控制台日志为例,启用SQL日志输出 dbContext.Database.Log = Console.WriteLine; // 执行查询 var result = query.ToList();
调整后,EF Core应该会生成类似你期望的SQL:
SELECT [t0].[Id], ( SELECT TOP (1) [t2].[Name] FROM [TaskStatus] AS [t1] INNER JOIN [Status] AS [t2] ON [t1].[StatusId] = [t2].[Id] WHERE [t1].[TaskId] = [t0].[Id] AND [t1].[Deleted] = 0 ORDER BY [t1].[CreatedDate] DESC ) AS [LastStatus] FROM [Tasks] AS [t0]
内容的提问来源于stack exchange,提问作者Zulander

