2010-09-21 3 views
9

W moim projekcie używam EntityFramework 4 do pracy z danymi. Znalazłem okropne problemy z wydajnością za pomocą prostego zapytania. Kiedy spojrzałem na profilera w zapytaniu sql wygenerowanym przez EF4, byłem zszokowany.Zapytania Linq w Entity Framework 4. Straszna wydajność

Mam kilka tabel w moim modelu danych jednostki:

Data Model

Wygląda to całkiem proste. Próbuję wybrać wszystkie produkty z określonej kategorii ze wszystkimi powiązanymi właściwościami nawigacyjnymi.

napisałem tej kwerendy 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 generowane tego zapytania 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 

Do 7000 rekordów w bazie danych i ~ 1000 rekord w podanej kategorii czas realizacji id tego zapytania za około 10 sekund . Nie jest zaskakujące, jeśli spojrzeć na to:

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 ....*** 

Zagnieżdżony wybrać się przyłączyć ... Straszne ... Próbowałem zmienić LINQ kwerendy, ale pojawia się samo zapytanie SQL wyprowadzane.

Rozwiązanie wykorzystujące procedury przechowywane jest dla mnie nie do przyjęcia, ponieważ używam bazy danych SQL Compact.

+3

Twój angielski nie jest taki zły :) Dobrze sformułowane pytanie też. +1 – Aren

+1

Możesz użyć http://imgur.com/, aby udostępnić obrazy. – Steven

+2

Co zawiera zestaw? Dlaczego nie tylko _od pi w ObjectSet gdzie pi.Product.CategoryId == categoryId wybierz pi_? –

Odpowiedz

7

Robisz Include("Product").Include("Inventory").Include("Inventory.Storage") i zastanawiasz się, dlaczego tak wiele rekordów jest pobieranych i dlaczego widzisz tak duże zapytanie SQL? Upewnij się, że rozumiesz, o co chodzi w metodzie Include. Jeśli chcesz prostszy zapytanie, należy użyć następujących:

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

Należy pamiętać jednak, że będzie to możliwe obciążenie Products, Inventories i Storages leniwie, co może powodować wiele więcej zapytań być wysłana, gdy iteracyjne nad tymi sub kolekcjach .

+0

+1 dobry punkt - z produktem: ProductItem (1: *) i ProductItem: Inventory (1: *), pojedynczy produkt będzie załaduj całe mnóstwo dodatkowych (prawdopodobnie niepotrzebnych) danych ... nic dziwnego, że jest powolny .... –

0

Myślę, że problem tkwi w kolekcji magazynowej w elemencie magazynu. Twoje zapytanie ograniczy produkty Product, ProductItem i Inventory wybrane do tych dla określonego CategoryId. Aby jednak wypełnić kolekcję zasobów elementu magazynu, kwerenda musi również zwrócić wszystkie wiersze zasobów, które używają tych samych danych StorageId (a następnie wszystkich odpowiadających im wierszy ProductItem i produktów dla tych dodatkowych rekordów spisu zasobów:

Zacznę od usunięcia kolekcji Zapasów z elementu Przechowywanie lub usunięcia odpowiadającego jej dodatku