2011-03-03 11 views
12

Estoy creando una aplicación con una capa de servicio (sitio web 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 Tasty Food Objetos.Entity Framework Seleccione un POCO nuevo sin .ToList() primero

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 (el tastyness es un entero que corresponde a nuestra enumeración tastyness).

me encuentro con este tipo de declaraciones mucho al hacer consultas de 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 .ToList() llamo consigo una excepción sobre LINQ no ser capaz de traducir el método personalizado a una consulta equivalente, lo cual entiendo.

Mi pregunta es acerca de la llamada a .ToList() antes de la.Elija (...) con la extensión personalizada para convertir nuestro objeto a la versión POCO del objeto de Alimentos.

¿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 List < ..>.

Respuesta

11

El problema con el uso de ToList o AsEnumerable es que se materializa toda la entidad y se paga el costo de la corrección. Si usted quiere tener la mejor posible SQL que devuelve sólo los campos necesarios, entonces usted debe proyectar directamente en lugar de utilizar .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 elenco es posible enumerar puede ser un problema. Si es así, pasar por 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 usted no paga el costo de la fijación de objetos en el ObjectContext.

+1

Sí. Tu primer ejemplo, con la proyección directa, es muy sabroso. –

+0

Muy bien, eso es exactamente de lo que estaba tratando de obtener una mejor explicación. En mi caso, utilizo todos los campos para el objeto en el Objeto de transferencia de datos (FoodData), por lo que el SQL probablemente no mejore demasiado. Esta información dará forma a la forma en que consulto las colecciones de entidades a partir de ahora. – Jacob

+0

más 1 para usar AsEnumerable() – mhand

6

Uso AsEnumerable() para activar la consulta en una antigua LINQ regular para objetos de consulta sin tener que crear una lista que no sean necesarios

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

+0

¿Tiene alguna información para respaldar que tiene un mejor rendimiento, mr. extraño en internet? ;) – Jacob

+0

@Jacob ver edición – cordialgerm

+0

Parece bastante sencillo. Todavía me gustaría intentar hacer algunos experimentos para grandes conjuntos de datos para ver la diferencia en el tiempo. – Jacob

0

La primera .ToList() no se requiere .

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.

¿Ha preguntado por 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.

+0

ToArray es más costoso que ToList ya que la implementación primero llena una lista y luego crea una matriz de la lista. – mhand

1

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 utilizar este ...

var deliciousFoods = entities.Foods 
         .Where(f => f.Tastyness == (int)Tastyness.Delicious) 
         .Select(Food.ConvertToFoodDataExpr()); 

Recuerde que al utilizar Marco de la entidad, que no se debe materializar el IEnumerable (usando ToList, ToArray etc.) se acaben aplicando la expresión de selección, de lo contrario Entity Framework no podrá realizar una declaración de selección correcta y siempre seleccionará todos los campos de la tabla.

Cuestiones relacionadas