【问题标题】:Entity Framework appears to be needlessly joining the same table twice实体框架似乎不必要地加入同一个表两次
【发布时间】:2012-09-05 20:14:38
【问题描述】:

更新这可能已经修复:http://entityframework.codeplex.com/workitem/486

...

针对我的实体的相当简单的 LINQ 语句会导致不必要的复杂 SQL。稍后会详细介绍,这是设置:

表格

出版

  • PublicationId (pk)
  • TopicId(转至主题表)
  • ReceiptCount(针对查询性能进行了非规范化处理)
  • 插入日期

收据

  • ReceiptId (pk)
  • PublicationId(参考上表)
  • 插入日期

LINQ

var query = from r in context.Receipts.Include("Publication")
            where r.DateInserted < lagDate
            && r.ReceiptId > request.AfterReceiptId
            && r.Publication.TopicId == topicEntity.TopicId
            && r.Publication.ReceiptCount > 1
            select r;

SQL

exec sp_executesql N'SELECT TOP (25) 
[Project1].[ReceiptId] AS [ReceiptId], 
[Project1].[PublicationId] AS [PublicationId], 
[Project1].[DateInserted] AS [DateInserted], 
[Project1].[DateReceived] AS [DateReceived], 
[Project1].[PublicationId1] AS [PublicationId1], 
[Project1].[PayloadId] AS [PayloadId], 
[Project1].[TopicId] AS [TopicId], 
[Project1].[BrokerType] AS [BrokerType], 
[Project1].[DateInserted1] AS [DateInserted1], 
[Project1].[DateProcessed] AS [DateProcessed], 
[Project1].[DateUpdated] AS [DateUpdated], 
[Project1].[PublicationGuid] AS [PublicationGuid], 
[Project1].[ReceiptCount] AS [ReceiptCount]
FROM ( SELECT 
    [Extent1].[ReceiptId] AS [ReceiptId], 
    [Extent1].[PublicationId] AS [PublicationId], 
    [Extent1].[DateInserted] AS [DateInserted], 
    [Extent1].[DateReceived] AS [DateReceived], 
    [Extent3].[PublicationId] AS [PublicationId1], 
    [Extent3].[PayloadId] AS [PayloadId], 
    [Extent3].[TopicId] AS [TopicId], 
    [Extent3].[BrokerType] AS [BrokerType], 
    [Extent3].[DateInserted] AS [DateInserted1], 
    [Extent3].[DateProcessed] AS [DateProcessed], 
    [Extent3].[DateUpdated] AS [DateUpdated], 
    [Extent3].[PublicationGuid] AS [PublicationGuid], 
    [Extent3].[ReceiptCount] AS [ReceiptCount]
    FROM   [dbo].[Receipt] AS [Extent1]
    INNER JOIN [dbo].[Publication] AS [Extent2] ON [Extent1].[PublicationId] = [Extent2].[PublicationId]
    LEFT OUTER JOIN [dbo].[Publication] AS [Extent3] ON [Extent1].[PublicationId] = [Extent3].[PublicationId]
    WHERE ([Extent2].[ReceiptCount] > 1) AND ([Extent1].[DateInserted] < @p__linq__0) AND ([Extent1].[ReceiptId] > @p__linq__1) AND ([Extent2].[TopicId] = @p__linq__2)
)  AS [Project1]
ORDER BY [Project1].[ReceiptId] ASC',N'@p__linq__0 datetime,@p__linq__1 int,@p__linq__2 int',@p__linq__0='2012-09-05 19:39:21:510',@p__linq__1=4458824,@p__linq__2=90

问题

出版物被加入两次:

  1. 因为.Include("Publication")而左外连接
  2. INNER JOIN 因为where

如果我从 SQL 中完全删除 [Extent2],并更改 WHERE 位以使用 [Extent3],我会得到相同的结果。由于我没有在我的实体上使用延迟加载,所以我必须 .Include("Publication")... 有什么解决方案吗?

我使用的是 EF4,但从 NuGet 获取 EF5 以查看它是否已修复,但它产生了相同的结果(尽管我不知道如何判断我的 EDMX 是否真的在使用 EF5)。

【问题讨论】:

  • 我认为你是完全正确的 where 和 include 都在做连接并且它没有简化为左连接。您应该将此作为对 EF Codeplex 项目的改进发布。但是我认为它的优先级会很低,因为我认为 SQL 会非常有效
  • "Even" NHibernate 有这样的问题。将其视为自动查询生成的一个特点。数据库引擎可能会优化它。不错的观察结果。
  • @GertArnold 它实际上导致了性能问题。 SQL Profiler 报告 Duration 为 700-900 ms,如果我删除它,我得到 0-6 ms。
  • 男孩!坏消息。看起来它毕竟需要改进。
  • @Langdon 您要删除哪个子句?

标签: linq entity-framework


【解决方案1】:

但是,有一种解决方法。它可能不是最优雅的解决方案,但它完全符合您的要求;它只生成一个连接。

变化:

var query = from r in context.Receipts.Include("Publication")    
            where r.DateInserted < lagDate 
            && r.ReceiptId > request.AfterReceiptId 
            && r.Publication.TopicId == topicEntity.TopicId 
            && r.Publication.ReceiptCount > 1 
            select r; 

成为:

var query = from r in context.Receipts
            join pub in context.Publication on r.PublicationId equals pub.PublicationId
            where r.DateInserted < lagDate 
            && r.ReceiptId > request.AfterReceiptId 
            && pub.TopicId == topicEntity.TopicId 
            && pub.ReceiptCount > 1 
            select new {
                Receipt = r,
                Publication = pub
            }; 

请注意,我们已经删除了 Include 并且我们不再使用 r.Publication。??在 where 子句中。相反,我们使用的是 pub.??

现在当你循环查询时,你会看到 r.Publication 不为空:

foreach ( var item in query)
{
    //see that item.Publication is not null
    if(item.Receipt != null && item.Receipt.Publication != null)
    {
        //do work based on a valid Publication
    }
    else
    {
        //do work based on no linked Publication
    }
}

【讨论】:

  • 我认为这将被解释为INNER JOIN 而不是LEFT JOIN
  • @edze 应该是内连接。 a INNER JOIN b AS b1 LEFT JOIN b AS b2(提示这个问题的双连接)永远不会为b2 返回空行,因为所有缺少b 行的as 已经被第一个连接过滤掉了。
  • 也就是说,同样的想法也应该适用于 r.Publication:删除原始查询中的 Include 并添加 select new { Receipt = r, Publication = r.Publication } 应该可以在不手动加入 PublicationId 的情况下工作
  • @hvd,事实上我首先尝试过,但又是 r.Publication。??在 where 子句中似乎是针对与 select 不同的 Join 执行的。
  • @saml 实际上现在正在测试自己,问题中的查询不会为我生成双重联接。如果我根据问题中生成的 SQL 添加.Take(25),它会这样做,但我得到的 SQL 仍然不同。我不确定原始查询真的是什么样子的。
【解决方案2】:

可以通过使用临时变量来避免这种行为(例如,let pub = r.Publication)。

var query = from r in context.Receipts
            let pub = r.Publication // using a temp variable
            where r.DateInserted < lagDate
            && r.ReceiptId > request.AfterReceiptId
            && pub.TopicId == topicEntity.TopicId
            && pub.ReceiptCount > 1
            select new { r, pub };

【讨论】:

  • 如果“出版”有了孩子,我该怎么办?
【解决方案3】:

我将通过更改以下代码来优化前人的答案,这消除了加入的需要,因此您不必知道需要加入哪些列,也不必在加入条件获得时更改 LINQ改变了。这应该是不必要的,但 MS 现在并不专注于修复他们的 sql 代码生成。

    var query = from pub in context.Publications
                from r in pub.Reciepts
                where r.DateInserted < lagDate 
                && r.ReceiptId > request.AfterReceiptId 
                && pub.TopicId == topicEntity.TopicId 
                && pub.ReceiptCount > 1 
                select new {
                       Receipt = r,
                       Publication = pub
                };

【讨论】:

    猜你喜欢
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多