2012-11-20 3 views
5

Ich hatte ein EF4-Modell in einer .NET 4.0-Anwendung, die ich auf .NET 4.5 und EF5 (Verweis auf die neue EntityFramework 5 Assembly) aktualisieren, änderte ich die "Code Generation Strategie "zu" None "und fügte dem Modell ein Codegenerierungselement (EF 5.x DbContext Generator) hinzu. Was in fast jeder Situation gut funktioniert. Aber jetzt habe ich große Probleme, wenn ich auf eine Navigationseigenschaft zugreife, die auf viele Datensätze verweist (> 100.000 Datensätze). Die Datenbank ist ein MSSQL 2005 Server.Leistungseinbußen nach dem Upgrade von EF4-Modell auf EF5-Modell (DbContext)

Mein Szenario sieht wie folgt aus:

Jeder Kunde in meinem db hat eine eindeutige ID (es ist der Primärschlüssel in der DB), zusätzlich jeder Kundendatensatz fast jeder einen Elternteil Kunden-ID (in diesem speziellen Fall enthält Kundenreferenzen auf die gleiche Eltern-ID (etwa 145.000 Datensätze aus 150.000 Datensätzen), die mit ID 1) aufgezeichnet werden.

Mein Modell enthält die DbSet<CustomerBase> CustomerBase, die die Tabelle darstellt, die alle Kunden und ihre Daten enthält. Zusätzlich gibt es die Navigationseigenschaften ICollection<CustomerBase> CustomerBaseChildren und ICollection<CustomerBase> CustomerBaseParent, die die Kunden-ID und die Kunden-Eltern-ID mit einer Multiplizität von 0..1 bis * verbinden.

Ich baue eine vereinfachte Version zu zeigen, was ich meine:

die Tabelle für diesen Test mit 150.000 Datensätze Körperbau:

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 

Import der Tabelle einschließlich der referencial Zwang in eine neue Einheit Modell. Ich habe als "Entity Container Name" ef5Entities verwendet. Dann habe ich die Navigation Properties CustomerBase1 und CustomerBase2 in CustomerBaseChildren und CustomerBaseParent umbenannt.

Und hier ist meine Beispielanwendung:

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(); 
} 

ich den SQL Server Profiler verwendet, um zu sehen, welche Rahmeneinheit auf der Datenbank ausgeführt wird. Es scheint, dass der EF4 und EF5 Code ist genau das gleiche:

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 

Wenn ich alle drei Anweisungen im SQL Management Studio ausführen, dauert es ca. 1-2 Sekunden, bis alle 1 + 1 + 150.000 Datensätze abgerufen werden.

Aber wie ich verstehe, ist die dritte Aussage das Problem. Es gibt 150.000 Datensätze (auch wenn ich .First() wie im obigen Code oder .Single() oder .Take(10) egal ob ich .OrderBy(...) davor oder nicht verwenden. Es scheint wie Entity Framework alle 150.000 Datensätze holen und Zwischenspeicherung der Datensätze in DbContext dauert schrecklich viel Zeit (nachdem ich 2 Minuten gewartet habe, habe ich den Testcode gestoppt, habe es mit meiner realen Kundenbasistabelle getestet, dauerte es 100 Minuten.) Das Caching in ObjectContext dauert nur etwa 10 Sekunden (was für die Datenbank 5 schlecht ist) -10-mal schneller, aber ich konnte das Leben mit, dass).

Auch der Speicherverbrauch ist schrecklich, mit Object der Anwendung Working Set über 200mb wirft, mit DbContext dem Working Set wirft etwa 10 mal höher.

Gibt es eine Möglichkeit, eine TOP (n) -Klausel in die select-Anweisung zu injizieren, um den Empfang aller Datensätze aus der Datenbank zu stoppen, wenn ich nur den ersten oder die ersten n Datensätze haben möchte (normalerweise 10 bis 100 Datensätze)? In der ersten Anweisung gab es eine TOP (1) in der Select-Anweisung (oder eine TOP (2) wenn .Single() anstelle von .First()).

ich sogar versucht, die Linie CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234); zu No-Tracking zu ändern: a mit der folgenden Meldung an CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First(); ein System.InvalidOperationExceptionCustomerBase someCustomer = context.CustomerBase.AsNoTracking().First(customer => customer.id == 1234);

Aber dann bekommen:

Wenn ein Objekt mit einer NoTracking Einarbeitungsoption zurückgeführt wird, Laden kann nur aufgerufen werden, wenn die EntityCollection oder EntityReference keine Objekte enthält.

Wenn ich die Code Generation Strategie wieder auf ObjectContext mit EF5 verwenden, funktioniert alles gut wie in guten alten EF4. Mache ich etwas falsch bei der Verwendung von DbContext oder ist DbContext in größeren Umgebungen nicht verwendbar?

Antwort

1

Ich habe vor kurzem auf Visual Studio 2013, .NET 4.5.1 und Entity Framework 6 aktualisiert. Wenn ich mein Modell so modifiziere, dass es EF6 anstelle von EF5 verwendet, funktioniert es wie ein Zauber.

Also die Lösung ist, EF4/EF5 mit ObjectContext oder EF6 mit DbContext zu verwenden.

Verwandte Themen