sql优化ef展平

tyg4sfes  于 2021-07-29  发布在  Java
关注(0)|答案(2)|浏览(401)

我有一个类似的例子:假设有很多工作要做,每项工作都有一个工人的历史,每个工作只有一个工人在工作。有三个表:作业本身,一个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中将其展平
谢谢你的帮助,如果我能以任何方式改进这个问题,请评论。

eagi6jfj

eagi6jfj1#

您可能已经看到有两种类型的linq方法:返回 IQueryable<...> ,还有其他的。
第一组的方法使用延迟执行。这意味着查询已完成,但尚未执行。未联系到您的数据库。
第二组的方法,如tolist()、firstordefault()、count()、any(),将执行查询:它们将联系数据库,并获取计算结果所需的数据。
这就是为什么你应该尽量推迟第二组的任何方法。如果你做得更早,然后再做一些关键的事情,那么改变就是你获取了大量的数据,或者,就像你的例子一样:你确实执行了两次相同的代码。
解决方法是:将第一个或默认值移到稍后的时刻。

var jobExample = dbContext.Jobs.Select(job => new
{
    Id = job.JobId,
    ... // other job properties

    ActiveWorker = job.JobWorkers
        .Where(jobWorker => jobWorker.To == null)
        .Select(worker => new
        {
            FirstName = worker.FirstName,
            LastName = worker.LastName,
        })
        .FirstOrDefault(),
})
.FirstOrDefault();

结果与您的略有不同:

Id = 10;
... // other Job properties

// the current active worker:
ActiveWorker =
{
     FirstName = "John",
     LastName = "Doe",
}

如果您确实想要一个id为/firstname/lastname的对象,请在最终firstordefault之前添加一个额外的select:

.Select(jobWithActiveWorker => new
{
    Id = jobWithActiveWorker.Id,
    ...  // other Job properties

    // properties of the current active worker
    FirstName = jobWithActiveWorker.FirstName,
    LastName = jobWithActiveWorker.LastName,
})
.FirstOrDefault();

我个人认为你不应该把工作属性和工人属性混为一谈,所以我认为第一个解决方案:“工作与当前活跃的工人”更整洁:工作属性与工人属性分开。如果您还需要活动工作进程的id,您可以看到为什么这一点很重要:

.Select(job => new
{
    Id = job.JobId,
    ... // other job properties

    ActiveWorker = job.JobWorkers
        .Where(jobWorker => jobWorker.To == null)
        .Select(jobworker => new
        {
            Id = jobworker.Id,
            FirstName = jobworker.FirstName,
            LastName = jobworker.LastName,
        })
        .FirstOrDefault(),
})
.FirstOrDefault();
epggiuax

epggiuax2#

请尝试这样重写查询:

var query =
    from j in dbContext.Jobs
    let ws = j.JobWorker
        .Where(jw => jw.To == null)
        .Select(jw => jw.Worker)
        .Take(1)
    from w in ws.DefaultIfEmpty()
    select new
    {
        j.JobId,
        // other properties
        w.FirstName,
        w.LastName,
    };

查询处理器可能无法进一步优化以知道它可以使用子查询一次。

相关问题