2012-11-20 13 views
5

Posiadałem model EF4 w aplikacji .NET 4.0, którą aktualizuję do wersji .NET 4.5 i EF5 (odnosząc się do nowego zestawu EntityFramework 5), zmieniłem "Generowanie kodu" Strategia "to" Brak "i dodano do modelu element generujący kod (EF 5.x DbContext Generator). Co działa dobrze w prawie każdej sytuacji. Ale teraz mam duże problemy, gdy mam dostęp do właściwości nawigacji, która odwołuje się do wielu rekordów (> 100 000 rekordów). Baza danych jest serwerem MSSQL 2005.Pogorszenie wydajności po uaktualnieniu modelu EF4 do modelu EF5 (DbContext)

Mój scenariusz wygląda następująco:

Każdy klient w moim db ma unikatowy identyfikator (jest to klucz podstawowy w DB), dodatkowo każdy rekord Klient zawierający identyfikator rodzic klienta (w tym szczególnym przypadku prawie każdego referencje klientów do tego samego identyfikatora nadrzędnego (około 145 000 rekordów z 150 000 rekordów), które są rekordowe z identyfikatorem 1).

Mój model zawiera DbSet<CustomerBase> CustomerBase, który przedstawia tabelę zawierającą wszystkie dane klientów. Ponadto istnieją właściwości nawigacyjne o nazwach ICollection<CustomerBase> CustomerBaseChildren i ICollection<CustomerBase> CustomerBaseParent, które łączą identyfikator klienta i identyfikator rodzica klienta z krotnościami od 0..1 do *.

zbudować uproszczoną wersję wykazać co mam na myśli:

Budowanie tabeli z 150.000 zapisów dla tego testu:

CREATE TABLE CustomerBase 
(
    id int IDENTITY(1,1) PRIMARY KEY NOT NULL, 
    parent_id int FOREIGN KEY REFERENCES CustomerBase(id), 
    some_data1 varchar(100), 
    some_data2 varchar(100), 
    some_data3 varchar(100), 
    some_data4 varchar(100), 
    some_data5 varchar(100), 
) 
GO 

DECLARE @i int = 0 
WHILE @i < 150000 BEGIN 
    INSERT INTO CustomerBase (parent_id, some_data1, some_data2, some_data3, some_data4, some_data5) VALUES (1, newid(), newid(), newid(), newid(), newid()) 

    SET @i = @i + 1 
END 

importu tabela tym referencial przymusu do nowego modelu jednostki. Użyłem jako "Entity Container Name" ef5Entities. Następnie zmieniłem nazwę CustomerBase1 i CustomerBase2 na Navigation Propierties na CustomerBaseChildren i CustomerBaseParent.

A oto moja przykładowa aplikacja:

static void Main(string[] args) 
{ 
    ef5Entities context = new ef5Entities(); 

    // Start with selecting a single customer. 
    // Works fine in EF4/ObjectContext, works even faster in in EF5/DbContext 
    CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234); 
    // Do something ... 

    // Get the parent of the customer. 
    // Works fine in EF4/ObjectContext, works even faster in in EF5/DbContext 
    CustomerBase parentCustomer = someCustomer.CustomerBaseParent; 
    // Do something ... 

    // Get the first child of the given parent id. 
    // Takes about 10 seconds in EF4/ObjectContext, I stopped the debugger after 2 minutes waiting in EF5/DbContext 
    CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First(); 

    Console.WriteLine("Press any key to quit."); 
    Console.ReadKey(); 
} 

użyłem SQL Server Profiler, aby zobaczyć co Entity Framework wykonuje się na bazie. Wydaje się, że kod EF4 i EF5 jest dokładnie taka sama:

SELECT TOP (1) 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5] 
FROM [dbo].[CustomerBase] AS [Extent1] 
WHERE 1234 = [Extent1].[id] 

exec sp_executesql N'SELECT 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5] 
FROM [dbo].[CustomerBase] AS [Extent1] 
WHERE [Extent1].[id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1 

exec sp_executesql N'SELECT 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5] 
FROM [dbo].[CustomerBase] AS [Extent1] 
WHERE [Extent1].[parent_id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1 

Gdybym wykonać wszystkie trzy instrukcje w SQL Management Studio, trwa około 1-2 sekund, aż wszystkie 1 + 1 + 150.000 zapisy są naciągane.

Ale jak rozumiem, trzecie stwierdzenie jest problemem. Zwraca 150.000 rekordów (nawet jeśli używam .First() jak w powyższym kodzie lub .Single() lub .Take(10), niezależnie od tego, czy używam przed nim .OrderBy(...). Wydaje się, że Entity Framework pobiera wszystkie 150.000 rekordów, a buforowanie rekordów w DbContext zajmuje okropne dużo czasu (po odczekaniu 2 minut, zatrzymałem kod testowy, testując go z moją prawdziwą tabelą bazową klienta trwało 100 minut) Caching w ObjectContext zajmuje tylko około 10 sekund (co jest złe biorąc pod uwagę, że sama baza danych to 5 -10 razy szybciej, ale może życie z tego).

Nawet zużycie pamięci jest strasznie, z ObjectContext aplikacja Zestaw roboczy podnosi około 200MB, z DbContext Set Working podnosi około 10 razy wyższa.

Czy istnieje sposób na wstrzyknięcie klauzuli TOP (n) w instrukcji select, aby przestać otrzymywać wszystkie rekordy z bazy danych, jeśli chcę tylko pierwszy rekord lub pierwsze n rekordów (zwykle od 10 do 100 rekordów)? W pierwszym stwierdzeniu było TOP (1) w instrukcji select (lub TOP (2), jeśli użyjemy .Single() zamiast .First()).

Próbowałem nawet zmienić linię CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234); do no-śledzenia: CustomerBase someCustomer = context.CustomerBase.AsNoTracking().First(customer => customer.id == 1234);

Ale wtedy pojawia się System.InvalidOperationException na CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First(); z następującym komunikatem:

Gdy obiekt jest zwracany z opcją NoTracking seryjnej, Wczytanie może zostać wywołane tylko wtedy, gdy EntityCollection lub EntityReference nie zawierają obiektów.

Jeśli zmienię strategię generowania kodu, aby używać ObjectContext z EF5, wszystko działa dobrze jak w starym dobrym EF4. Czy robię coś nie tak podczas korzystania z DbContext lub DbContext po prostu nie nadaje się do użytku w większych środowiskach?

Odpowiedz

1

Niedawno uaktualniono do Visual Studio 2013, .NET 4.5.1 i Entity Framework 6. Jeśli zmienię mój model, aby używał EF6 zamiast EF5, działa jak urok.

Rozwiązaniem jest więc użycie EF4/EF5 z ObjectContext lub EF6 z ​​DbContext.

Powiązane problemy