2012-11-20 3 views
5

Avevo un modello EF4 in un'applicazione .NET 4.0 che aggiorno a .NET 4.5 e EF5 (facendo riferimento al nuovo assieme EntityFramework 5), ho modificato la "Generazione del codice" Strategia "su" Nessuno "e aggiunto un elemento di generazione del codice (EF 5.x DbContext Generator) al modello. Che funziona bene in quasi tutte le situazioni. Ma ora ho grossi problemi quando accedo a una proprietà di navigazione che fa riferimento a molti record (> 100.000 record). Il database è un server MSSQL 2005.Degrado delle prestazioni dopo l'aggiornamento del modello EF4 al modello EF5 (DbContext)

mio scenario è simile al seguente:

Ogni cliente nel mio db ha un ID univoco (E 'la chiave primaria nel DB), oltre ogni record cliente contiene un ID cliente principale (in questo caso particolare quasi tutti riferimenti del cliente allo stesso id padre (circa 145.000 record su 150.000 record) che è registrato con ID 1).

Il mio modello contiene lo DbSet<CustomerBase> CustomerBase che rappresenta la tabella che contiene tutti i clienti e i loro dati. Inoltre, vi sono proprietà di navigazione chiamate ICollection<CustomerBase> CustomerBaseChildren e ICollection<CustomerBase> CustomerBaseParent che collegano l'ID cliente e l'ID padre cliente con una molteplicità da 0..1 a *.

costruisco una versione semplificata per dimostrare quello che voglio dire:

costruire la tabella con 150.000 record per questo test:

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 

importare la tabella compreso il vincolo referenziale in un nuovo modello di entità. Ho usato come "Entity Container Name" ef5Entities. Quindi ho rinominato Navigation Propierties CustomerBase1 e CustomerBase2 in CustomerBaseChildren e CustomerBaseParent.

E qui è la mia applicazione di esempio:

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

ho usato lo SQL Server Profiler per vedere cosa Entity Framework esegue sul database. Sembra che il codice EF4 e EF5 è esattamente la stessa:

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 

Se eseguo tutte e tre le istruzioni nel SQL Management Studio, ci vogliono circa 1-2 secondi fino a quando tutti i 1 + 1 + 150.000 record vengono recuperati.

Ma come ho capito la terza affermazione è il problema. Restituisce 150.000 record (anche se io uso .First() come nel codice sopra o .Single() o .Take(10) non importa se io uso o meno .OrderBy(...). Sembra che Entity Framework recuperi tutti i 150.000 record e la memorizzazione nella cache dei record in DbContext richiede un pessimo molto tempo (dopo aver atteso 2 minuti ho interrotto il codice di test, testandolo con la mia vera tabella di base dei clienti ci sono voluti 100 minuti per terminare). Il caching in ObjectContext richiede solo circa 10 secondi (il che è negativo considerando che il database stesso è 5 -10 volte più veloce, ma ho potuto vita con quella).

Anche il consumo di memoria è orribilmente, con ObjectContext l'applicazione working set solleva circa 200MB, con DbContext del gruppo di lavoro solleva di circa 10 volte superiore.

C'è un modo per iniettare una clausola TOP (n) nell'istruzione select per interrompere la ricezione di tutti i record dal database se voglio solo il primo record oi primi n record (solitamente da 10 a 100 record)? Nella prima affermazione c'era un TOP (1) nell'istruzione select (o un TOP (2) se si utilizza .Single() invece di .First()).

Ho anche provato a cambiare la linea CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234); al no-tracking: CustomerBase someCustomer = context.CustomerBase.AsNoTracking().First(customer => customer.id == 1234);

Ma poi un ottengo un System.InvalidOperationException al CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First(); con il seguente messaggio:

Quando un oggetto viene restituito con un'opzione NoTracking unione, Il carico può essere chiamato solo quando EntityCollection o EntityReference non contengono oggetti.

Se cambio di nuovo la strategia di generazione del codice per utilizzare ObjectContext con EF5, tutto funziona come nel vecchio EF4. Sto facendo qualcosa di sbagliato durante l'utilizzo di DbContext o DbContext non è utilizzabile in ambienti più grandi?

risposta

1

Recentemente ho aggiornato a Visual Studio 2013, .NET 4.5.1 e Entity Framework 6. Se modifico il mio modello per utilizzare EF6 invece di EF5 funziona come un incantesimo.

Quindi la soluzione è utilizzare EF4/EF5 con ObjectContext o EF6 con DbContext.