Entity Framework, кажется, бесполезно соединяет одну и ту же таблицу дважды

Обновление Возможно, это уже исправлено: http://entityframework.codeplex.com/workitem/486

...

Довольно простая инструкция LINQ для моих объектов приводит к излишне сложному SQL. Подробнее об этом позже, здесь настройка:

Таблицы

Публикация

  • PublicationId (pk)
  • ТемаId (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

Проблема

Публикация объединяется дважды:

  • LEFT OUTER JOIN из-за .Include("Publication")
  • INNER JOIN из-за where.

Если я полностью удалю [Extent2] из SQL и изменим биты WHERE, чтобы использовать [Extent3], я получаю те же результаты. Поскольку я не использую Lazy Loading на своих объектах, я должен .Include("Publication")... есть ли какое-либо решение для этого?

Я использовал EF4, но схватил EF5 от NuGet, чтобы убедиться, что он был исправлен, но он дает тот же результат (хотя я не знаю, как сказать, действительно ли мой EDMX использует EF5).

Ответ 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 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
    }
}

Ответ 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
                };