Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 747
Jacob Avatar asked Mar 03 '11 20:03

Jacob


3 Answers

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.

like image 75
Craig Stuntz Avatar answered Nov 02 '22 11:11

Craig Stuntz


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

like image 24
cordialgerm Avatar answered Nov 02 '22 11:11

cordialgerm


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.

like image 2
Björn Boxstart Avatar answered Nov 02 '22 11:11

Björn Boxstart