La query di Entity Framework è lenta, ma lo stesso SQL in SqlQuery è veloce


93

Vedo delle prestazioni davvero strane relative a una query molto semplice utilizzando Entity Framework Code-First con .NET framework versione 4. La query LINQ2Entities ha il seguente aspetto:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Questa operazione richiede oltre 3000 millisecondi per l'esecuzione. L'SQL generato sembra molto semplice:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Questa query viene eseguita quasi istantaneamente quando viene eseguita tramite Management Studio. Quando cambio il codice C # per utilizzare la funzione SqlQuery, viene eseguito in 5-10 millisecondi:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Quindi, esattamente lo stesso SQL, le entità risultanti vengono tracciate in entrambi i casi, ma una differenza di prestazioni selvaggia tra i due. Cosa succede?


2
Mi aspetto che tu stia riscontrando ritardi nell'inizializzazione, probabilmente visualizza la compilazione. Vedi MSDN:Performance Considerations for Entity Framework 5
Nicholas Butler

Ho provato a pre-generare visualizzazioni e non sembra aiutare. Inoltre, è stata eseguita un'altra query EF prima di quella lenta per escludere cose di inizializzazione. La nuova query è stata eseguita rapidamente, quella lenta è stata ancora eseguita lentamente, anche se durante la prima query è stato eseguito il riscaldamento del contesto.
Brian Sullivan

1
@marc_s - No, SqlQuery restituirà un'istanza di entità completamente materializzata e rilevata dalle modifiche. Vedi msdn.microsoft.com/en-us/library/…
Brian Sullivan

L'SQL generato per la query EF in realtà include il valore del parametro o utilizza un parametro? Ciò non dovrebbe influire sulla velocità delle query per una singola query, ma potrebbe causare il gonfiore di queryplan nel server nel tempo.
Jim Wooley

Hai provato a eseguire la stessa query due volte / più volte? Quanto tempo ci è voluto per la seconda volta? Hai provato questo su .NET Framework 4.5: ci sono alcuni miglioramenti delle prestazioni relativi a EF in .NET Framework 4.5 che potrebbero aiutare.
Pawel

Risposte:


96

Trovato. Si scopre che è un problema di tipi di dati SQL. La SomeStringPropcolonna nel database era un varchar, ma EF presuppone che i tipi di stringa .NET siano nvarchar. Il processo di traduzione risultante durante la query per il DB per eseguire il confronto è ciò che richiede molto tempo. Penso che EF Prof mi abbia portato un po 'fuori strada qui, una rappresentazione più accurata della query in esecuzione sarebbe la seguente:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Quindi la soluzione risultante è annotare il modello code-first, indicando il tipo di dati SQL corretto:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}

1
Bella indagine. La tua query soffriva di "conversione implicita", come spiegato qui: brentozar.com/archive/2012/07/…
Jaime

Mi ha risparmiato alcune ore di debug. Questo era esattamente il problema.
Cody

1
Nel mio caso, sto usando EDMX con un database legacy, che utilizza varcharper tutto, e in effetti questo era il problema. Mi chiedo se posso creare un EDMX per considerare varchar per ogni colonna di stringa.
Alisson

1
Ottimo uomo di scoperta. ma @Jaime cosa dovremmo fare per il primo approccio al database poiché tutto (ad esempio l'annotazione dei dati sui modelli db) si cancella dopo aver aggiornato il modello EF dal database.
Nauman Khan

Impostandolo come home page per un po 'così posso rivivere l'emozione di trovare di nuovo una risposta così eccezionale per un po'. Grazie!!!
OJisBad

43

Il motivo per rallentare le mie query effettuate in EF era il confronto tra scalari non nullable e scalari nullable:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Quella query ha richiesto 35 secondi. Ma un piccolo refactoring come quello:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

dà risultati incredibili. Ci sono voluti solo 50 ms per completare. È possibile che si tratti di un bug in EF.


13
È così strano
Daniel Cardenas

1
OH MIO DIO. Apparentemente questo può accadere anche quando si utilizzano le interfacce IUserId.Id stava causando il problema con me, ma prima mappare l'ID su un intero funziona ... devo controllare ora tutte le query nella mia applicazione da 100.000 righe?
Dirk Boer

è stato segnalato questo bug? È ancora nell'ultima versione 6.2.0
Dirk Boer

2
Lo stesso problema è anche in EF Core. Grazie per averlo trovato!
Yannickv

Un altro suggerimento è elaborare la variabile prima di inserirla nell'espressione LINQ. Altrimenti lo sql generato sarà molto più lungo e lento. Ho sperimentato quando ho Trim () e ToLower () all'interno dell'espressione LINQ che mi infastidisce.
samheihey


4

Ho avuto lo stesso problema (la query è veloce se eseguita da SQL manager) ma quando eseguita da EF il timeout scade.

Risulta che l'entità (che è stata creata dalla vista) aveva chiavi di entità sbagliate. Quindi l'entità aveva righe duplicate con le stesse chiavi e immagino che doveva fare il raggruppamento sullo sfondo.


3

Mi sono anche imbattuto in questo problema con una query ef complessa. Una correzione per me che ha ridotto una query ef di 6 secondi alla seconda query sql generata è stata quella di disattivare il caricamento lento.

Per trovare questa impostazione (ef 6) vai al file .edmx e guarda in Proprietà -> Generazione codice -> Caricamento lento abilitato. Impostato su falso.

Enorme miglioramento delle prestazioni per me.


4
È bello, ma non ha nulla a che fare con la domanda sui poster.
Jace Rhea

2

Anch'io ho avuto questo problema. Si scopre che il colpevole nel mio caso era lo sniffing dei parametri di SQL-Server .

Il primo indizio che il mio problema fosse in realtà dovuto allo sniffing dei parametri era che l'esecuzione della query con "set arithabort off" o "set arithabort on" produceva tempi di esecuzione drasticamente diversi in Management Studio. Ciò è dovuto al fatto che ADO.NET utilizza per impostazione predefinita "set arithabort off" e Management Studio utilizza per impostazione predefinita "set arithabort on". La cache del piano di query mantiene piani diversi a seconda di questo parametro.

Ho disabilitato la memorizzazione nella cache del piano di query per la query, con la soluzione che puoi trovare qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.