LINQ to Entity Framework — несколько путей включения приводят к плохому TSQL

#tsql #entity-framework #entity-framework-4 #linq-to-entities

#tsql #entity-framework #entity-framework-4 #linq-to-entities

Вопрос:

Задан следующий запрос LINQ to Entities (EF4)…

 var documents =
    from doc in context.Documents
        .Include(d => d.Batch.FinancialPeriod)
        .Include(d => d.Batch.Contractor)
        .Include(d => d.GroupTypeHistory.Select(gth => gth.GroupType))
        .Include(d => d.Items.Select(i => i.Versions))
        .Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.HomeDelivery)))
        .Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.Manufacturer)))
        .Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPeriodic)))
        .Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.SpecialContainerIndicator)))
        .Include(d => d.Items.Select(i => i.Versions.Select(v => v.Endorsements.Select(e => e.Periodic))))
        where doc.ID == this.documentID
        select doc;

Document document = documents.FirstOrDefault();
  

Где …

  • у документа всегда будет пакет, который, в свою очередь, всегда будет иметь финансовый период и исполнителя
  • документ всегда будет иметь одну или несколько GroupTypeHistory, каждая из которых всегда будет иметь groupType
  • документ будет содержать ноль или более элементов, которые, в свою очередь, будут иметь одну или несколько версий
  • версия будет иметь нулевой или один ProductPackPeriodic
  • в ProductPackPeriodic всегда будут один ProductPack и один ProductPeriodic, а также ноль или один SpecialContainerIndicator
  • пакет продуктов всегда будет содержать один продукт
  • у продукта будет ноль или один производитель и ноль или одна доставка на дом
  • версия будет иметь ноль или более подтверждений, каждое из которых будет иметь одно периодическое

Приведенный выше запрос LINQ генерирует один из худших TSQL, которые я когда-либо видел, причем некоторые связанные таблицы включаются несколько раз (вероятно, потому, что на них несколько раз ссылаются в запросе) и занимает значительно больше времени, чем я хотел бы выполнить (соответствующие таблицы могут содержать миллионы строк, но причина не в этом).

Я знаю, что должен быть лучший способ написать это (принимая во внимание все различные ссылочные типы, которые я описал выше), что приведет к улучшению TSQL, но каждая версия, которую я пробую, не возвращает данные правильно.

Кто-нибудь может помочь указать мне на лучшее решение?

Если это облегчает понимание, если бы я писал TSQL напрямую, я бы смотрел на что-то вроде следующего…

 select *
from Document d
    inner join Batch b 
        inner join FinancialPeriod fp on b.FinancialPeriodID = fp.FinancialPeriodID
        inner join Contractor c on b.ContractorID = c.ContractorID
    on d.BatchID = b.BatchID
    inner join DocumentGroupType dgt on d.DocumentID = dgt.DocumentID
    left join Item i
        left join ItemVersion iv
            left join ProductPackPeriodic ppp
                inner join ProductPack pack 
                    inner join Product p 
                        left join Manufacturer m on p.ManufacturerID = m.ManufacturerID
                        left join HomeDeliveryProduct hdp on p.ProductID = hdp.ProductID
                    on pack.ProductID = p.ProductID
                on ppp.ProductPackID = pack.ProductPackID
                inner join ProductPeriodic pp on ppp.ProductPeriodicID = pp.ProductPeriodicID
                left join SpecialContainerIndicator sci on ppp.SpecialContainerIndicatorCode = sci.SpecialContainerIndicatorCode
            on iv.ProductPackPeriodicID = ppp.ProductPackPeriodicID
            left join ItemVersionEndorsement ive 
                inner join EndorsementPeriodic ep on ive.EndorsementPeriodicID = ep.EndorsementPeriodicID
            on iv.ItemVersionID = ive.ItemVersionID
        on i.ItemID = iv.ItemID
    on d.DocumentID = i.DocumentID
where d.DocumentID = 33 -- example value
  

Это также может прояснить требования к взаимосвязи.

Заранее спасибо.

Ответ №1:

Для подобных сценариев я пишу специальные хранимые процедуры, а затем использую EFExtensions с пользовательским материализатором, чтобы не только получить отличную производительность, но и правильно материализовать объекты.

Комментарии:

1. Отличный ответ; это значительно улучшило производительность, как я надеялся, и (как вы предложили) позволяет мне указать мой собственный TSQL. Спасибо.

Ответ №2:

У меня нет хорошего ответа для EF, но вам может подойти использование micro ORM для определенных сложных запросов. Микро-ORM — это, по сути, низкоуровневые оболочки поверх SQL, которые позволяют вам получать строго типизированные объекты наряду с другими удобными функциями. Вы можете взглянуть, например, на Dapper, который используется этим самым сайтом для некоторых запросов с узким местом. Он должен работать очень близко к производительности собственного SQL.