.Include () vs .Load () performance en EntityFramework

Al consultar una tabla grande donde necesita acceder a las propiedades de navegación más adelante en el código (explícitamente no quiero usar la carga diferida), ¿qué tendrá un mejor rendimiento .Include() o .Load() ? ¿O por qué usar uno sobre el otro?

En este ejemplo, las tablas incluidas solo tienen alrededor de 10 entradas y los empleados tienen alrededor de 200 entradas, y puede suceder que la mayoría de ellas se carguen de todos modos con inclusión porque coinciden con la cláusula where.

 Context.Measurements.Include(m => m.Product) .Include(m => m.ProductVersion) .Include(m => m.Line) .Include(m => m.MeasureEmployee) .Include(m => m.MeasurementType) .Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1)) .ToList(); 

o

 Context.Products.Load(); Context.ProductVersions.Load(); Context.Lines.Load(); Context.Employees.Load(); Context.MeasurementType.Load(); Context.Measurements.Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1)) .ToList(); 

La respuesta es “depende, pruebe ambos”.

Al usar Include() , obtiene el beneficio de cargar todos sus datos en una sola llamada al almacén de datos subyacente. Si se trata de un servidor SQL remoto, por ejemplo, puede ser un gran impulso en el rendimiento.

La desventaja es que las consultas de Include() tienden a ser realmente complicadas, especialmente si tiene filtros ( Where() llamadas, por ejemplo) o intenta hacer cualquier agrupación. EF generará consultas anidadas con inserciones sub- SELECT y APPLY para obtener los datos que desee. También es mucho menos eficiente: recupera una sola fila de datos con cada columna posible de objetos secundarios, de modo que los datos para sus objetos de nivel superior se repetirán muchas veces. (Por ejemplo, un objeto monoparental con 10 hijos producirá 10 filas, cada una con los mismos datos para las columnas del objeto principal). He tenido consultas EF individuales tan complejas que han causado lockings cuando se ejecutan al mismo tiempo que EF actualizar la lógica.

El método Load() es mucho más simple. Cada consulta es una statement SELECT simple, sencilla y directa en una sola tabla. Estos son mucho más fáciles en todas las formas posibles, excepto que tienes que hacer muchos de ellos (posiblemente muchas veces más). Si ha nested colecciones de colecciones, incluso puede necesitar recorrer sus objetos de nivel superior y Load sus sub-objetos. Se puede ir de las manos.

Como regla general rápida, trato de evitar tener más de tres llamadas Include en una sola consulta. Encuentro que las consultas de EF se ponen feas para reconocer más allá de eso; también coincide con mi regla de oro para las consultas de SQL Server, que hasta cuatro instrucciones JOIN en una sola consulta funciona muy bien, pero después de eso es hora de considerar la refactorización.

Sin embargo, todo eso es solo un punto de partida. Depende de su esquema, su entorno, sus datos y muchos otros factores. Al final, solo tendrás que probarlo en todos los sentidos. Escoja un patrón razonable “predeterminado” para usar, vea si es lo suficientemente bueno, y si no, optimícelo al gusto.

Include() se escribirá en SQL como JOIN : una base de datos de ida y vuelta.

Cada instrucción Load() es “cargando explícitamente de forma diferida” las entidades solicitadas, por lo tanto, una ida y vuelta de base de datos por llamada.

Por lo tanto, es muy probable que Include() sea ​​la opción más sensata en este caso, pero depende del diseño de la base de datos, de la frecuencia con la que se llama a este código y de la duración de su DbContext . ¿Por qué no intentas ambas formas y perfilas las consultas y comparas los tiempos?

Ver Cargando Entidades Relacionadas .

Estoy de acuerdo con @MichaelEdenfield en su respuesta, pero quería comentar sobre el escenario de colecciones anidadas. Puede moverse teniendo que hacer bucles nesteds (y las muchas llamadas resultantes a la base de datos) girando la consulta al revés.

En lugar de recorrer una colección de Órdenes del Cliente y luego realizar otro ciclo nested a través de la colección OrderItems del Pedido, puede consultar los Artículos de pedido directamente con un filtro como el siguiente.

 context.OrderItems.Where(x => x.Order.CustomerId == customerId); 

Obtendrá los mismos datos resultantes que las cargas dentro de los bucles nesteds, pero con solo una llamada a la base de datos.

Además, hay un caso especial que debe considerarse con Includes. Si la relación entre el padre y el hijo es uno a uno, entonces el problema con los datos principales que se devuelven varias veces no sería un problema.

No estoy seguro de cuál sería el efecto si el caso de la mayoría fuera donde no existía ningún niño: ¿muchos nulos? Los niños dispersos en una relación uno a uno podrían ser más adecuados para la técnica de consulta directa que describí anteriormente.

Include es un ejemplo de carga ansiosa, donde no solo carga las entidades que está consultando, sino también todas las entidades relacionadas.

Load es una anulación manual de EnableLazyLoading . Si este está configurado a false . Todavía puedes cargar la entidad que pediste con la .Load()

Siempre es difícil decidir si ir con Eager, Explicit o incluso Lazy Loading.
Lo que recomiendo de todos modos siempre es realizar algunos perfiles. Esa es la única forma de asegurarse de que su solicitud sea efectiva o no.
Hay muchas herramientas que te ayudarán. Eche un vistazo a este artículo de Julie Lerman donde enumera varias maneras diferentes de hacer perfiles. Una solución simple es comenzar a perfilar en su SQL Server Management Studio .
No dude en hablar con un DBA (si tiene cerca de usted) que lo ayudará a comprender el plan de ejecución.
También podría echar un vistazo a esta presentación donde escribí una sección sobre cómo cargar datos y rendimiento.

Una cosa más para agregar a este hilo. Depende del servidor que uses. Si está trabajando en el servidor sql, está bien usar la carga ansiosa, pero para sqlite deberá usar .Load () para evitar la carga cruzada. Causa de la excepción sqlite no puede tratar con algunas declaraciones include que van más allá de un nivel de dependencia