Linq-запросы в Entity Framework 4. Ужасная производительность - PullRequest
9 голосов
/ 21 сентября 2010

В своем проекте я использую EntityFramework 4 для работы с данными.Я обнаружил ужасные проблемы с производительностью с помощью простого запроса.Когда я посмотрел на профилировщик SQL-запроса, сгенерированного EF4, я был шокирован.

У меня есть несколько таблиц в моей модели данных сущностей:

Data Model

Это выглядит довольно просто.Я пытаюсь выбрать все товары из указанной категории со всеми связанными навигационными свойствами.

Я написал этот запрос LINQ:

ObjectSet<ProductItem> objectSet = ...; 
int categoryId = ...; 

var res = from pi in objectSet.Include("Product").Include("Inventory").Include("Inventory.Storage") 
where pi.Product.CategoryId == categoryId 
select pi;

EF сгенерировал этот SQL-запрос:

SELECT   [Project1].[pintId1]          AS [pintId], 
[Project1].[pintId]           AS [pintId1], 
[Project1].[intProductId]     AS [intProductId], 
[Project1].[nvcSupplier]      AS [nvcSupplier], 
[Project1].[ nvcArticle]      AS [ nvcArticle], 
[Project1].[nvcBarcode]       AS [nvcBarcode], 
[Project1].[bIsActive]        AS [bIsActive], 
[Project1].[dtDeleted]        AS [dtDeleted], 
[Project1].[pintId2]          AS [pintId2], 
[Project1].[nvcName]          AS [nvcName], 
[Project1].[intCategoryId]    AS [intCategoryId], 
[Project1].[ncProductType]    AS [ncProductType], 
[Project1].[C1]               AS [C1], 
[Project1].[pintId3]          AS [pintId3], 
[Project1].[intProductItemId] AS [intProductItemId], 
[Project1].[intStorageId]     AS [intStorageId], 
[Project1].[dAmount]          AS [dAmount], 
[Project1].[mPrice]           AS [mPrice], 
[Project1].[dtModified]       AS [dtModified], 
[Project1].[pintId4]          AS [pintId4], 
[Project1].[nvcName1]         AS [nvcName1], 
[Project1].[bIsDefault]       AS [bIsDefault] 
FROM     (SELECT [Extent1].[pintId]         AS [pintId], 
[Extent1].[intProductId]   AS [intProductId], 
[Extent1].[nvcSupplier]    AS [nvcSupplier], 
[Extent1].[ nvcArticle]    AS [ nvcArticle], 
[Extent1].[nvcBarcode]     AS [nvcBarcode], 
[Extent1].[bIsActive]      AS [bIsActive], 
[Extent1].[dtDeleted]      AS [dtDeleted], 
[Extent2].[pintId]         AS [pintId1], 
[Extent3].[pintId]         AS [pintId2], 
[Extent3].[nvcName]        AS [nvcName], 
[Extent3].[intCategoryId]  AS [intCategoryId], 
[Extent3].[ncProductType]  AS [ncProductType], 
[Join3].[pintId1]          AS [pintId3], 
[Join3].[intProductItemId] AS [intProductItemId], 
[Join3].[intStorageId]     AS [intStorageId], 
[Join3].[dAmount]          AS [dAmount], 
[Join3].[mPrice]           AS [mPrice], 
[Join3].[dtModified]       AS [dtModified], 
[Join3].[pintId2]          AS [pintId4], 
[Join3].[nvcName]          AS [nvcName1], 
[Join3].[bIsDefault]       AS [bIsDefault], 
CASE 
WHEN ([Join3].[pintId1] IS NULL) THEN CAST(NULL AS int) 
ELSE 1 
END AS [C1] 
FROM   [ProductItem] AS [Extent1] 
INNER JOIN [Product] AS [Extent2] 
ON [Extent1].[intProductId] = [Extent2].[pintId] 
LEFT OUTER JOIN [Product] AS [Extent3] 
ON [Extent1].[intProductId] = [Extent3].[pintId] 
LEFT OUTER JOIN (SELECT [Extent4].[pintId]           AS [pintId1], 
[Extent4].[intProductItemId] AS [intProductItemId], 
[Extent4].[intStorageId]     AS [intStorageId], 
[Extent4].[dAmount]          AS [dAmount], 
[Extent4].[mPrice]           AS [mPrice], 
[Extent4].[dtModified]       AS [dtModified], 
[Extent5].[pintId]           AS [pintId2], 
[Extent5].[nvcName]          AS [nvcName], 
[Extent5].[bIsDefault]       AS [bIsDefault] 
FROM   [Inventory] AS [Extent4] 
INNER JOIN [Storage] AS [Extent5] 
ON [Extent4].[intStorageId] = [Extent5].[pintId]) AS [Join3] 
ON [Extent1].[pintId] = [Join3].[intProductItemId] 
WHERE  [Extent2].[intCategoryId] = 8 /* @p__linq__0 */) AS [Project1] 
ORDER BY [Project1].[pintId1] ASC, 
[Project1].[pintId] ASC, 
[Project1].[pintId2] ASC, 
[Project1].[C1] ASC

Для 7000 записей в базе данных и ~ 1000 записей в указанной категории идентификатор времени выполнения этого запроса составляет около 10 секунд.Не удивительно, если взглянуть на это:

FROM [ProductItem] AS [Extent1]
INNER JOIN [Product] AS [Extent2]
ON [Extent1].[intProductId] = [Extent2].[pintId]
LEFT OUTER JOIN [Product] AS [Extent3]
ON [Extent1].[intProductId] = [Extent3].[pintId]
***LEFT OUTER JOIN (SELECT ....***

Вложенный выбор в соединении ... Ужасно ... Я пытался изменить запрос LINQ, но мне выводится тот же SQL-запрос.

Решение с использованием хранимых процедур для меня неприемлемо, потому что я использую базу данных SQL Compact.

Ответы [ 2 ]

7 голосов
/ 21 сентября 2010

Вы делаете Include("Product").Include("Inventory").Include("Inventory.Storage"), и вам интересно, почему выбирается так много записей и почему вы видите такой большой запрос SQL? Пожалуйста, убедитесь, что вы понимаете, что такое Include метод. Если вы хотите более простой запрос, используйте следующее:

var res =
    from pi in objectSet
    where pi.Product.CategoryId == categoryId 
    select pi;

Обратите внимание, однако, что это будет возможно загружать Products, Inventories и Storages лениво, что может привести к отправке гораздо большего количества запросов, когда вы выполняете итерацию по этим подколлекциям.

0 голосов
/ 22 сентября 2010

Я думаю, что проблема с коллекцией Inventory в элементе Storage.Ваш запрос ограничит элементы Product, ProductItem и Inventory, выбранные для указанных в CategoryId.Однако, чтобы заполнить коллекцию Inventory элемента Storage, запрос также должен вернуть все строки Inventory, которые используют один и тот же StorageId (а затем все соответствующие строки ProductItem и Product для этих дополнительных записей Inventory.

Я бы начал с удаления коллекции Inventory из элемента Storage или удалил соответствующее включение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...