linq - query - sql command entity framework
Entity Framework Seleccione nuevo POCO sin.ToList() primero (4)
Estoy creando una aplicación con una capa de servicio (sitio web de WCF) y un cliente de Silverlight 4. Los servicios de RIA no son una opción, por lo que creamos clases intermedias para pasar de ida y vuelta. A los efectos de esta pregunta, supongamos que estoy pasando adelante y atrás por Tasty Food
Objects.
public class FoodData
{
public int Id { get; set; }
public string Name { get; set; }
public Tastyness TastyLevel { get; set; }
}
El Modelo EF es esencialmente la misma clase, una tabla con tres campos básicos (la Sabiduría es una int que corresponde a nuestra Enum Tastyness).
Me encuentro usando este tipo de afirmación mucho cuando hago consultas a Entity Framework:
public List<FoodData> GetDeliciousFoods()
{
var deliciousFoods = entities.Foods
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
.ToList() // Necessary? And if so, best performance with List, Array, other?
.Select(dFood => dFood.ToFoodData())
.ToList();
return deliciousFoods;
}
Sin la llamada .ToList () obtengo una excepción sobre que LINQ no puede traducir el método personalizado a una consulta equivalente, lo que entiendo.
Mi pregunta es acerca de la llamada a .ToList () antes de .Select (...) con la extensión personalizada para convertir nuestro objeto a la versión POCO del objeto Food.
¿Hay un mejor patrón para hacer aquí, o tal vez incluso una mejor alternativa a .ToList () que puede ser más eficaz ya que realmente no requieren la funcionalidad del resultado de la Lista <...>.
El primer .ToList () no es obligatorio.
var deliciousFoods = entities.Food
// Here a lazy-evaluated collection is created (ie, the actual database query
// has not been run yet)
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
// With ToArray, the query is executed and results returned and
// instances of Food created. The database connection
// can safely be closed at this point.
// Given the rest of the linq query, this step can be skipped
// with no performance penalty that I can think of
.ToArray()
// Project result set onto new collection. DB Query executed if
// not already
// The above .ToArray() should make no difference here other
// than an extra function call an iteration over the result set
.Select(dFood => dFood.ToFoodData())
// This one might not be needed, see below
.ToList();
¿Requiere que el conjunto de resultados sea una Lista <>? ¿O solo una IEnumerable o ICollection sería adecuada? Si es así, entonces el último .ToList () puede no ser necesario.
¿Preguntaste sobre el rendimiento? ¿Cuántas instancias espera que se devuelvan por consulta? Si es relativamente poco, entonces .ToList () o .ToArray () u otros no hacen una diferencia significativa. ¿Se trata más de qué tipo de funcionalidad necesitas para exponer? Si el objeto devuelto necesita ser indexable, susceptible de ser agregado y tener las otras propiedades de List, está bien. Pero, si lo único que estás haciendo es iterar sobre la colección devuelta, no expongas lo que no se necesita.
El problema con el uso de ToList
o AsEnumerable
es que materializas toda la entidad y pagas el costo de la corrección. Si desea tener el mejor SQL posible que devuelve solo los campos necesarios, entonces debe proyectar directamente en lugar de usar .ToFoodData()
:
var deliciousFoods = entities.Foods
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
.Select(dFood => new FoodData
{
Id = dFood.Id,
Name = dFood.Name,
TastyLevel = (Tastyness)dFood.Tastyness
});
El lanzamiento a enum puede ser un problema. Si es así, ve a través de un tipo anónimo:
var deliciousFoods = entities.Foods
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
.Select(dFood => new FoodData
{
Id = dFood.Id,
Name = dFood.Name,
TastyLevel = dFood.Tastyness
})
.AsEnumerable()
.Select(dFood => new FoodData
{
Id = dFood.Id,
Name = dFood.Name,
TastyLevel = (Tastyness)dFood.TastyLevel
});
Si examina el SQL resultante, verá que es más simple y no paga el costo de arreglar objetos en ObjectContext.
Utilice AsEnumerable()
para convertir la consulta en una consulta LINQ antigua normal a Objetos sin tener que crear una Lista innecesaria
var deliciousFoods = entities.Foods
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
.AsEnumerable()
.Select(dFood => dFood.ToFoodData())
.ToList();
Editar: ver http://www.hookedonlinq.com/AsEnumerableOperator.ashx
La respuesta de @Craig Stuntz es correcta, sin embargo, puede haber un problema cuando tienes varias consultas que deberían transformar un objeto ''Comida'' en un objeto ''FoodData''. No desea que la expresión se duplique en varias ubicaciones (DRY).
La solución puede ser no tener un método que realmente devuelva un objeto ''FoodData'', sino tener un método que devuelva la expresión que se utilizará para realizar la transformación. Luego puede volver a usar este método.
Class Food {
...
public static Expression<Func<Food, FoodData> ConvertToFoodDataExpr() {
Expression<Func<Food, FoodData>> expr = dFood => new FoodData
{
Id = dFood.Id,
Name = dFood.Name,
TastyLevel = dFood.Tastyness
}
}
}
Y para usar esto ...
var deliciousFoods = entities.Foods
.Where(f => f.Tastyness == (int)Tastyness.Delicious)
.Select(Food.ConvertToFoodDataExpr());
Recuerde al usar Entity Framework, que no debe materializar IEnumerable (usando ToList, ToArray, etc.) antes de aplicar la expresión de selección, de lo contrario, Entity Framework no podrá realizar una instrucción de selección correcta y siempre seleccionará todos los campos de la mesa.