La consulta de Entity Framework es lenta, pero el mismo SQL en SqlQuery es rápido

93

Veo un rendimiento realmente extraño relacionado con una consulta muy simple usando Entity Framework Code-First con .NET Framework versión 4. La consulta LINQ2Entities se ve así:

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

Esto tarda más de 3000 milisegundos en ejecutarse. El SQL generado parece muy simple:

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

Esta consulta se ejecuta casi instantáneamente cuando se ejecuta a través de Management Studio. Cuando cambio el código C # para usar la función SqlQuery, se ejecuta en 5-10 milisegundos:

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

Entonces, exactamente el mismo SQL, las entidades resultantes tienen un seguimiento de cambios en ambos casos, pero hay una gran diferencia de rendimiento entre las dos. ¿Lo que da?

Brian Sullivan
fuente
2
Supongo que está viendo retrasos en la inicialización, probablemente vea la compilación. Ver MSDN:Performance Considerations for Entity Framework 5
Nicholas Butler
Intenté generar vistas previas y no parece ayudar. Además, ejecutó otra consulta EF antes de la lenta para descartar cosas de inicialización. La nueva consulta se ejecutó rápidamente, la lenta aún se ejecutó lentamente, a pesar de que se produjo un calentamiento del contexto durante la primera consulta.
Brian Sullivan
1
@marc_s: No, SqlQuery devolverá una instancia de entidad completamente materializada y con seguimiento de cambios. Ver msdn.microsoft.com/en-us/library/…
Brian Sullivan
¿El SQL generado para su consulta EF está realmente alineado con el valor del parámetro o está usando un parámetro? Esto no debería afectar la velocidad de la consulta para una consulta individual, pero podría hacer que el plan de consultas se hinche en el servidor con el tiempo.
Jim Wooley
¿Ha intentado ejecutar la misma consulta dos o varias veces? ¿Cuánto tiempo tardó en ejecutarse la segunda vez? ¿Ha probado esto en .NET Framework 4.5? Hay algunas mejoras de rendimiento relacionadas con EF en .NET Framework 4.5 que podrían ayudar.
Pawel

Respuestas:

96

Lo encontré. Resulta que es un problema de tipos de datos SQL. La SomeStringPropcolumna de la base de datos era un varchar, pero EF asume que los tipos de cadena .NET son nvarchars. El proceso de traducción resultante durante la consulta para que la base de datos haga la comparación es lo que lleva mucho tiempo. Creo que EF Prof me estaba desviando un poco aquí, una representación más precisa de la consulta que se está ejecutando sería la siguiente:

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

Entonces, la solución resultante es anotar el modelo de código primero, indicando el tipo de datos SQL correcto:

public class MyTable
{
    ...

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

    ...
}
Brian Sullivan
fuente
1
Buena investigación. Su consulta sufría de "conversión implícita", como se explica aquí: brentozar.com/archive/2012/07/…
Jaime
Me ahorró algunas horas de depuración. Este era exactamente el problema.
Cody
1
En mi caso, estoy usando EDMX con una base de datos heredada, que se usa varcharpara todo, y de hecho, este era el problema. Me pregunto si puedo hacer un EDMX para considerar varchar para todas las columnas de cadenas.
Alisson
1
Gran hallazgo hombre. pero @Jaime, lo que debemos hacer para el primer enfoque de la base de datos, ya que todo (por ejemplo, la anotación de datos en los modelos db) se borra después de actualizar el modelo EF de la base de datos.
Nauman Khan
Establecer esto como mi página de inicio por un tiempo para poder revivir la emoción de encontrar una respuesta tan buena nuevamente por un tiempo. ¡¡¡Gracias!!!
OJisBad
43

La razón de ralentizar mis consultas realizadas en EF fue comparar escalares no anulables con escalares anulables:

long? userId = 10; // nullable scalar

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

Esa consulta tomó 35 segundos. Pero una pequeña refactorización como esa:

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

da resultados increíbles. Solo tardó 50 ms en completarse. Es posible que sea un error en EF.

cryss
fuente
13
Esto es tan extraño
Daniel Cárdenas
1
DIOS MIO. Aparentemente, esto también puede suceder cuando uso las interfaces IUserId.Id estaba causando el problema conmigo, pero la primera asignación de Id a un número entero funciona ... ¿tengo que verificar ahora todas las consultas en mi aplicación de 100.000 líneas?
Dirk Boer
¿Se ha informado de este error? Todavía está en la última versión 6.2.0
Dirk Boer
2
El mismo problema también está en EF Core. ¡Gracias por encontrar esto!
Yannickv
Otra sugerencia es procesar la variable antes de ponerla en la expresión LINQ. De lo contrario, el sql generado será mucho más largo y lento. Experimenté al tener Trim () y ToLower () dentro de la expresión LINQ que me molesta.
samheihey
4

Tuve el mismo problema (la consulta es rápida cuando se ejecuta desde el administrador SQL) pero cuando se ejecuta desde EF, el tiempo de espera expira.

Resulta que la entidad (que se creó a partir de la vista) tenía claves de entidad incorrectas. Entonces, la entidad tenía filas duplicadas con las mismas claves, y supongo que tuvo que agrupar en el fondo.

Vladimir Gedgafov
fuente
3

También encontré esto con una consulta ef compleja. Una solución para mí que redujo una consulta ef de 6 segundos a la segunda consulta SQL que generó fue desactivar la carga diferida.

Para encontrar esta configuración (ef 6) vaya al archivo .edmx y busque en Propiedades -> Generación de código -> Carga diferida habilitada. Establecer en falso.

Gran mejora en el rendimiento para mí.

user2622095
fuente
4
Eso es genial, pero no tiene nada que ver con la cuestión de los carteles.
Jace Rhea
2

Yo tuve este problema también. Resulta que el culpable en mi caso fue el rastreo de parámetros de SQL-Server .

La primera pista de que mi problema se debió de hecho al rastreo de parámetros fue que ejecutar la consulta con "set arithabort off" o "set arithabort on" produjo tiempos de ejecución drásticamente diferentes en Management Studio. Esto se debe a que ADO.NET usa por defecto "establecer arithabort desactivado" y Management Studio por defecto "establecer arithabort activado". La caché del plan de consulta mantiene diferentes planes en función de este parámetro.

Inhabilité el almacenamiento en caché del plan de consulta para la consulta, con la solución que puede encontrar aquí .

Oskar Sjöberg
fuente