我有一个类似的例子:假设有很多工作要做,每项工作都有一个工人的历史,每个工作只有一个工人在工作。有三个表:作业本身,一个Map表jobworkers,它保存给定作业的工人历史记录(包括一个datetime“to”,它指示是否仍处于活动状态(null)或何时取消分配(结束日期)),以及具有名字和姓氏的工人。我想查询所有工作的列表,以及当前作为平面模型分配的工人的名字和姓氏。这是我正在执行的代码:
var jobExample = dbContext.Jobs.Select(j => new
{
j.JobId,
// ...some other columns from jobs table
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.FirstName, // first name of currently assigned worker
j.JobWorker.FirstOrDefault(jw => jw.To == null).Worker.LastName // last name of currently assigned worker
}).First();
将生成以下sql查询:
SELECT TOP (1)
[Extent1].[JobId] AS [JobId],
[Extent3].[FirstName] AS [FirstName],
[Extent5].[LastName] AS [LastName]
FROM [tables].[Jobs] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent2]
WHERE ([Extent1].[JobId] = [Extent2].[JobId]) AND ([Extent2].[To] IS NULL) ) AS [Limit1]
LEFT OUTER JOIN [tables].[Workers] AS [Extent3] ON [Limit1].[WorkerId] = [Extent3].[WorkerId]
OUTER APPLY (SELECT TOP (1)
[Extent4].[WorkerId] AS [WorkerId]
FROM [tables].[JobWorkers] AS [Extent4]
WHERE ([Extent1].[JobId] = [Extent4].[JobId]) AND ([Extent4].[To] IS NULL) ) AS [Limit2]
LEFT OUTER JOIN [tables].[Workers] AS [Extent5] ON [Limit2].[WorkerId] = [Extent5].[WorkerId]
可以看到,有两个完全相同的外部apply/left外部联接。我想去掉其中一个以使查询更有效。请注意,select语句是根据用户实际想要查询的信息动态生成的。但即使这不适用,我也不知道如何在没有层次结构的情况下做到这一点,然后才在.net中将其展平
谢谢你的帮助,如果我能以任何方式改进这个问题,请评论。
2条答案
按热度按时间eagi6jfj1#
您可能已经看到有两种类型的linq方法:返回
IQueryable<...>
,还有其他的。第一组的方法使用延迟执行。这意味着查询已完成,但尚未执行。未联系到您的数据库。
第二组的方法,如tolist()、firstordefault()、count()、any(),将执行查询:它们将联系数据库,并获取计算结果所需的数据。
这就是为什么你应该尽量推迟第二组的任何方法。如果你做得更早,然后再做一些关键的事情,那么改变就是你获取了大量的数据,或者,就像你的例子一样:你确实执行了两次相同的代码。
解决方法是:将第一个或默认值移到稍后的时刻。
结果与您的略有不同:
如果您确实想要一个id为/firstname/lastname的对象,请在最终firstordefault之前添加一个额外的select:
我个人认为你不应该把工作属性和工人属性混为一谈,所以我认为第一个解决方案:“工作与当前活跃的工人”更整洁:工作属性与工人属性分开。如果您还需要活动工作进程的id,您可以看到为什么这一点很重要:
epggiuax2#
请尝试这样重写查询:
查询处理器可能无法进一步优化以知道它可以使用子查询一次。