如何改写Linq查询以替换Take(1)并保持功能与性能
解决Linq查询中Take(1)生成低效SQL的替代方案
原查询的核心需求是:查询所有Workflow,并标记每个Workflow是否存在对应的Search记录。原写法用Take(1)+左连接生成了低效的ROW_NUMBER()分区SQL,以下是两种不用Take的改写方案,均能保持IQueryable特性并生成高效SQL:
方案1:使用Exists子查询(最优,贴合需求)
直接通过Any判断是否存在关联记录,无需取出实际的Search数据,生成的SQL会用EXISTS子查询,性能远优于分区排序:
(from workflow in db.Workflows select new { workflow, hasSearch = db.Searches.Any(p => p.WorkflowId == workflow.WorkflowId) }).ToList();
对应的SQL类似:
SELECT w.*, CASE WHEN EXISTS (SELECT 1 FROM Searches s WHERE s.WorkflowId = w.WorkflowId) THEN 1 ELSE 0 END AS hasSearch FROM Workflows w
方案2:子查询取单条记录(若需用到Search其他字段)
如果后续需要用到Search的其他字段,可改用FirstOrDefault作为子查询,ORM会将其翻译为SELECT TOP 1的关联子查询,而非ROW_NUMBER():
(from workflow in db.Workflows let search = db.Searches.Where(p => p.WorkflowId == workflow.WorkflowId).FirstOrDefault() select new { workflow, hasSearch = search != null && search.SearchId > 0 }).ToList();
对应的SQL类似:
SELECT w.*, CASE WHEN s.SearchId > 0 THEN 1 ELSE 0 END AS hasSearch FROM Workflows w LEFT JOIN ( SELECT TOP 1 s.* FROM Searches s WHERE s.WorkflowId = w.WorkflowId ) s ON 1=1
注:
FirstOrDefault在Linq to Entities查询链中不会立即执行,依然会保持IQueryable特性,只有调用ToList()时才会触发SQL执行。
内容的提问来源于stack exchange,提问作者KBell
相关产品推荐
相关产品推荐

