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.InvalidOperationException
CustomerBase 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?