实体框架似乎不必要地两次加入同一个表

时间:2012-09-05 20:14:38

标签: linq entity-framework

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

...

针对我的实体的相当简单的LINQ语句导致不必要的复杂SQL。更多相关内容,请点击以下设置:

表格

公开

  • PublicationId(pk)
  • TopicId(fk到主题表)
  • ReceiptCount(针对查询性能进行非规范化)
  • DateInserted

收据

  • ReceiptId(pk)
  • PublicationId(fk到上表)
  • DateInserted

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. 而左外联接
  3. INNER JOIN因为where
  4. 如果我完全从SQL中删除[Extent2],并更改WHERE位以使用[Extent3],我会得到相同的结果。由于我没有在我的实体上使用延迟加载,因此我必须.Include("Publication") ...是否有任何解决方案?

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

3 个答案:

答案 0 :(得分:5)

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

变化:

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 AND我们不再使用r.Publication。在where子句中。相反,我们正在使用pub。??

现在,当您循环查询时,您将看到r.Publication不为null:

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
    }
}

答案 1 :(得分: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 };

答案 2 :(得分:0)

我将通过更改下面的代码来优化之前的人的答案,这消除了连接的需要,因此您不必知道需要加入哪些列,也不必在连接条件获得时更改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
                };