2011-09-30 29 views
23

Consulte esta línea de código. Esta es una invocación de un procedimiento almacenado, que devuelve un ObjectResult<long?>. Con el fin de extraer los valores largos añadí el Select:.NET Entity Framework - IEnumerable VS. IQueryable

dbContext.FindCoursesWithKeywords(keywords).Select(l => l.Value); 

Basado en intellisense esta SELECT devuelve IEnumerable<long>.

No estoy seguro de si lo leí en algún lado o tal vez me acostumbre a esta suposición: siempre pensé que cuando la API de EF devuelve IEnumerable (y no IQueryable) significa que los resultados se han materializado. Lo que significa que han sido sacados de la base de datos.

Descubrí hoy que estaba equivocado (¿o quizás es un error?). Seguí recibiendo el error

"Nueva transacción no está permitido porque hay otros hilos que se ejecutan en la sesión"

Básicamente, este error le indica que usted está tratando de guardar los cambios mientras que el db reader todavía está leyendo registros.

el tiempo me lo resolvió por (lo que consideraba una posibilidad muy remota) y se añadió ToArray() llamada a materializar la ...

Así IEnumerable<long> - la línea de fondo - debo esperar IEnumerable resultados de EF para contener los resultados de ese refugio ¿Ya se materializó? En caso afirmativo, ¿hay alguna manera de saber si se ha materializado o no un IEnumerable?

Gracias y disculpas si esta es una de esas preguntas 'duhhh' ... :)

+0

No estoy seguro si hay algo documentado específicamente para EF, pero para Linq en general, un 'IEnumerable ' no es más "materializado" que un 'IQueryable ' - generalmente se supone que ambos usan ejecución diferida. –

+0

@Damien_The_Unbeliever: Sin embargo, creo que ObjectResult siempre se ejecutará cuando se llame a la función ... (en este caso FindCoursesWithKeywords) –

Respuesta

27

IQueryable se usa cuando está utilizando Linq-to-entities = usted está creando una consulta declarativa LINQ en su aplicación que será interpretada por el proveedor de LINQ como SQL y se ejecutará en el servidor. Una vez que la consulta se ejecuta (iterada), se convertirá en IEnumerable y los objetos se materializarán según sea necesario para la iteración = no de forma inmediata.

Una vez que llame al procedimiento almacenado no está utilizando Linq-to-entities porque no hay una consulta declarativa integrada en su aplicación. La consulta/SQL ya existe en el servidor de la base de datos y solo lo está invocando. Esto devolverá IEnumerable pero nuevamente no materializará todos los resultados inmediatamente. Los resultados se materializarán como iterados. Este es el principio del cursor de base de datos/o del lector de datos .NET cuando solicita explícitamente la búsqueda de un objeto.

Así que si se llama a algo como esto:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords) 
           .Select(l => l.Value)) 
{ 
    ... 
} 

estás obteniendo los cursos de uno a uno (por cierto, ¿por qué cargar curso completo si usted está interesado sólo en palabras clave.?). Hasta que complete o rompa el ciclo, su lector de datos se abrirá para buscar registros.

Si en lugar de llamar a esto:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords) 
           .ToList() // or ToArray 
           .Select(l => l.Value)) 
{ 
    ... 
} 

Va a obligar a consulta para materializar todos los resultados inmediatamente y bucle llevará a cabo en la recogida en la memoria en lugar de lector de base de datos abierta.

Diferencia entre IEnumerable y IQueryable no está en la forma en cómo los datos se recuperan porque IQueryable es IEnumerable. La diferencia está en la construcción de respaldo (algo debe implementar estas interfaces).

+0

Hola, gracias por esta explicación!Entonces, como se trata de un proceso almacenado, no devolverá un IQueryable, ya que no admite todas las características que una consulta hace, ¿verdad? (Por cierto, estoy buscando cursos, no palabras clave) :) – justabuzz

10

trabajando en un IEnumerable<T> significa que todas las demás operaciones que sucederá en código C#, es decir LINQ a objetos. No significa que la consulta ya se haya ejecutado.

Una vez que se degrada a linq-to-objects, todos los datos que quedan en este punto deben obtenerse de la base de datos y enviarse a .net. Esto puede degradar el rendimiento drásticamente (por ejemplo, los índices de bases de datos no serán utilizados por linq-to-objects), pero por otro lado linq-to-objects es más flexible, ya que puede ejecutar código C# arbitrario en lugar de estar limitado por su proveedor de linq puede traducir a SQL.

A IEnumerable<T> puede ser una consulta diferida o datos ya materializados. Los operadores estándar de linq normalmente se difieren, y ToArray()/ToList() siempre se materializan.

+0

¡Gracias por esta distinción! Entonces, ¿es algo de lo que debería estar consciente? ¿Hay alguna implicación o problema? ¡Aclamaciones! – justabuzz

+0

El problema más obvio es que la degeneración a 'IEnumerable ' puede reducir drásticamente el rendimiento. Imagine que se degrada al principio de la consulta, entonces es probable que se deba buscar la tabla completa en lugar de filtrar usando SQL en el servidor usando índices en la base de datos para acelerar. Por lo tanto, en el punto en que se degrada a 'IEnumerable ', quiere que le queden pocos elementos. – CodesInChaos

+0

No estoy seguro de que esto sea correcto. Leyendo más, hice + la otra respuesta aquí, digo algo similar: IEnumerable puede que no se materialice hasta más tarde cuando lo itere. Si entiendo bien, esto se debe a que cuando ejecutas un proceso almacenado obtienes un IQueryable completo, porque este no es un objeto completo consultable. ¿Tiene sentido? :) – justabuzz

-5

IEnumerable: LINQ to Object y LINQ to XML.

IQueryable: LINQ to SQL

+0

IQueryable es un tipo que aún no se ha ejecutado. Básicamente es la "consulta". http://msdn.microsoft.com/en-us/library/system.linq.iqueryable(v=vs.100).ASPX Virutally no tiene nada que ver con LINQ to SQL o Linq to Anything. Es una interfaz para proporcionar acceso a cualquier tipo de proveedor de datos. – Tony

0

IEnumerable no hidratar hasta que se materializó. Si llama a un procedimiento almacenado, pensaría que no se necesita más filtro, es decir, que envíe parámetros a un procedimiento almacenado para producir el subconjunto deseado de datos devueltos. IEnumerable vinculado a un procedimiento almacenado está bien. Sin embargo, si está buscando todo el contenido de una tabla, entonces, al filtrar en la aplicación, debe tener una estrategia. Como, no ToList() el IEnumerable de la tabla, materializará todas las filas. Algunas veces esto causará una excepción de falta de memoria. Además de por qué consumir memoria sin razón. Use IQueryable contra el contexto, de esa manera puede filtrar la tabla en el origen de datos, y no en la aplicación. En respuesta, tienes que materializarlo. IEnumerable es una interfaz, solo materializándola "inicializará" su tipo y producirá algo a mi entender.

Cuestiones relacionadas