开发者

Entity Framework Select new POCO without .ToList() first

I'm creating an application with a service layer (WCF Website) and a Silverlight 4 Client. RIA Services are not an option, so 开发者_StackOverflow中文版we create intermediary classes to pass back and forth. For the purpose of this question let's assume I'm passing back and forth Tasty Food Objects.

public class FoodData
{
  public int Id { get; set; }
  public string Name { get; set; }
  public Tastyness TastyLevel { get; set; }
}

The EF Model is essentially the same class, a table with three basic fields (the Tastyness is an int that corresponds to our enum Tastyness).

I find myself using this kind of statement a lot when doing Entity Framework queries:

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;
}

Without the .ToList() call I get an exception about LINQ not being able to translate the custom method to a query equivalent, which I understand.

My question is about the call to .ToList() before the .Select(...) with the custom extension to convert our object to the POCO version of the Food object.

Is there a better pattern to do here, or maybe even a better alternative to .ToList() that may be more performant since I don't really require the functionality of the List<..> result.


The problem with using ToList or AsEnumerable is that you materialize the entire entity and pay the cost of fixup. If you want to have the best possible SQL which returns only the needed fields, then you should project directly rather than using .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
                                  });

The cast to enum may be a problem. If so, go through an anonymous type:

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
                                  });

If you examine the resulting SQL, you'll see it's simpler, and you don't pay the cost of fixing up objects into the ObjectContext.


Use AsEnumerable() to turn the query into a regular old LINQ to Objects query without having to create an unneeded List

var deliciousFoods = entities.Foods
                               .Where(f => f.Tastyness == (int)Tastyness.Delicious)
                               .AsEnumerable()
                               .Select(dFood => dFood.ToFoodData())
                               .ToList();

Edit: See http://www.hookedonlinq.com/AsEnumerableOperator.ashx


The answer of @Craig Stuntz is correct, however there could be an issue when you have multiple queries that should transform a 'Food' object into a 'FoodData' object. You don't want the expression to be duplicated in multiple locations (DRY).

The solution can be to not have a method that actually returns a 'FoodData' object, but to have a method that returns the expression to be used to make the transformation. You can then re-use this method.

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
    }
  }
}

And to use this...

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

Remember when using Entity Framework, that you shouldn't materialize the IEnumerable (using ToList, ToArray etc.) befor applying the select expression, otherwise Entity Framework won't be able to make a correct select statement and it will always select all fields from the table.


The first .ToList() isn't required.

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();

Do you require that the result set be a List<>? Or would just an IEnumerable or ICollection be adequate? If so, then the last .ToList() may not be needed.

You asked about performance? How many instances do you expect to be returned per query? If it's relatively few, then .ToList() or .ToArray(), or others don't make any meaningful difference. It's more about what kind of functionality do you need to expose? If the returned object needs to be indexable, addable, and have the other properties of List, that's okay. But, if all you're doing is iterating over the returned collection, don't expose what isn't needed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜