2012-09-20 19 views
6

leí el Loading Related Entities mensaje por el equipo Entity Framework y un poco confundido por el último párrafo:¿El LINQ con un resultado escalar disparar la carga diferida

A veces es útil saber cuántas entidades están relacionados con otra entidad en la base de datos sin incurrir realmente en el costo de cargar todas esas entidades. El método Query con el método LINQ Count se puede usar para hacer esto. Por ejemplo:

using (var context = new BloggingContext()) 
{ 
    var blog = context.Blogs.Find(1); 

    // Count how many posts the blog has 
    var postCount = context.Entry(blog) 
          .Collection(b => b.Posts) 
          .Query() 
          .Count(); 
} 

¿Por qué el método Query + Count para continuar?
¿No podemos simplemente usar el método COUNT de LINQ?

var blog = context.Blogs.Find(1); 
var postCount = blog.Posts.Count(); 

Will que desencadenan la carga lenta y toda la colección será cargado a la memoria y justo que voy a conseguir mi valor escalar deseada?

Respuesta

2

El primer método no se está cargando todas las filas ya que el método Count se invoca desde un IQueryable pero el segundo método se está cargando todas las filas, ya que se invoca desde un ICollection.

Hice algunas pruebas para verificarlo. Lo probé con Table1 y Table2, Table1 tiene el PK "Id" y Table2 tiene el FK "Id1" (1: N). Utilicé el perfilador EF desde aquí http://efprof.com/.

Primer método:

var t1 = context.Table1.Find(1); 

var count1 = context.Entry(t1) 
         .Collection(t => t.Table2) 
         .Query() 
         .Count(); 

No Select * From Table2:

SELECT TOP (2) [Extent1].[Id] AS [Id] 
FROM [dbo].[Table1] AS [Extent1] 
WHERE [Extent1].[Id] = 1 /* @p0 */ 

SELECT [GroupBy1].[A1] AS [C1] 
FROM (SELECT COUNT(1) AS [A1] 
     FROM [dbo].[Table2] AS [Extent1] 
     WHERE [Extent1].[Id1] = 1 /* @EntityKeyValue1 */) AS [GroupBy1] 

Segundo método:

var t1 = context.Table1.Find(1); 
var count2 = t1.Table2.Count(); 

Tabla2 se carga en memoria:

SELECT TOP (2) [Extent1].[Id] AS [Id] 
FROM [dbo].[Table1] AS [Extent1] 
WHERE [Extent1].[Id] = 1 /* @p0 */ 

SELECT [Extent1].[Id] AS [Id], 
     [Extent1].[Id1] AS [Id1] 
FROM [dbo].[Table2] AS [Extent1] 
WHERE [Extent1].[Id1] = 1 /* @EntityKeyValue1 */ 

¿Por qué sucede esto?

El resultado de Collection(t => t.Table2) es una clase que implementa ICollection pero no está cargando todas las filas y tiene una propiedad llamada IsLoaded. El resultado del método Query es un IQueryable y esto permite llamar al Count sin precargar filas.

El resultado de t1.Table2 es ICollection y está cargando todas las filas para obtener el conteo. Por cierto, incluso si usa solo t1.Table2 sin pedir el conteo, las filas se cargan en la memoria.

+0

Gracias Amiram, por lo que pensé que era correcto, el getter de propiedad de la colección desencadena la carga lenta. Lo estoy aceptando – gdoron

6

Obtendrá el valor escalar deseado en casos de bot. Pero considera la diferencia en lo que está sucediendo.

Con .Query().Count() ejecuta una consulta en la base de datos del formulario SELECT COUNT(*) FROM Posts y asigna ese valor a su variable entera.

Con .Posts.Count, ejecuta (algo así como) SELECT * FROM Posts en la base de datos (mucho más caro). Cada fila del resultado se correlaciona campo por campo en su tipo de objeto C#, ya que la colección se enumera para encontrar su conteo. Al solicitar el recuento de esta manera, está forzando que se carguen todos los datos para que C# cuente cuánto hay.

Afortunadamente es obvio que pedirle a la base de datos el recuento de filas (sin devolver realmente todas esas filas) es mucho más eficiente.

+0

Sé la diferencia entre los dos, me pregunto por qué el primero no desencadena la carga lenta mientras que el segundo sí lo está. Creo que la respuesta es el hecho de que cuando usas el captador de la colección desencadena la carga lenta. ¿Estoy en lo cierto? – gdoron

+0

@AmiramKorach. Si crees que está mal, escribe una respuesta completa que explique por qué. Gracias amigo. – gdoron

1

La primera solución no desencadena la carga diferida porque probablemente nunca acceda directamente a la propiedad de recopilación. El método Collection acepta Expression, no solo delega. Se usa solo para obtener el nombre de la propiedad que se utiliza para acceder a la información de mapeo y generar una consulta correcta.

Incluso si tuviera acceso a la propiedad de colección, podría usar la misma estrategia que otras partes internas de EF (por ejemplo, validación) que desactiva temporalmente la carga diferida antes de acceder a las propiedades de navegación para evitar cargas perezosas inesperadas.

Btw. esta es una gran mejora en contraste con la API ObjectContext donde la consulta de compilación requiere el acceso a la propiedad de navegación y, por lo tanto, puede desencadenar la carga diferida.

Hay una diferencia más entre esos dos enfoques:

  • La primera siempre se ejecuta la consulta a la base de datos y devuelve el recuento de elementos en la base de datos
  • El segundo ejecuta la consulta a la base de datos sólo una vez para cargar todos los artículos y luego devuelve conteos de elementos en la aplicación sin verificar el estado en la base de datos

Como la tercera opción bastante interesante, puede usar carga adicional.The implementation by Arthur Vickers muestra cómo usar la propiedad de navegación para obtener el recuento de la base de datos sin elementos de carga diferida.

+0

+1. Gracias por su respuesta y tiempo! – gdoron

Cuestiones relacionadas