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?