实体框架首先选择没有.ToList()的新POCO

时间:2011-03-03 20:37:33

标签: linq linq-to-sql entity-framework linq-to-entities

我正在创建一个带有服务层(WCF网站)和Silverlight 4客户端的应用程序。 RIA服务不是一种选择,因此我们创建了来回传递的中间类。出于这个问题的目的,让我们假设我来回传递美味Food对象。

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

EF模型本质上是同一个类,一个包含三个基本字段的表(Tastyness是一个与我们的enum Tastyness相对应的int)。

在进行实体框架查询时,我发现自己经常使用这种语句:

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

没有.ToList()调用,我得到一个关于LINQ无法将自定义方法转换为等价查询的异常,我理解。

我的问题是在 .Select(...)之前调用 .ToList()并使用自定义扩展将我们的对象转换为POCO版本食物对象。

这里有更好的模式,或者甚至可能是更好的替代.ToList()可能更高效,因为我并不真正需要List&lt; ..&gt;的功能结果。

4 个答案:

答案 0 :(得分:11)

使用ToListAsEnumerable的问题在于您实现了整个实体并支付了修复费用。如果你想拥有最好的SQL,只返回所需的字段,那么你应该直接投影而不是使用.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
                                  });

演员到枚举可能是一个问题。如果是这样,请通过匿名类型:

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

如果检查生成的SQL,您会发现它更简单,并且您不需要支付将对象修复到ObjectContext的成本。

答案 1 :(得分:6)

使用AsEnumerable()将查询转换为常规的旧LINQ to Objects查询,而无需创建不需要的列表

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

修改:请参阅http://www.hookedonlinq.com/AsEnumerableOperator.ashx

答案 2 :(得分:1)

@Craig Stuntz的答案是正确的,但是当您有多个查询将“Food”对象转换为“FoodData”对象时,可能会出现问题。您不希望表达式在多个位置(DRY)重复。

解决方案可能是没有实际返回'FoodData'对象的方法,而是有一个方法返回用于进行转换的表达式。然后,您可以重复使用此方法。

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

并使用此...

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

请记住,在使用Entity Framework时,您不应该实现IEnumerable(使用ToList,ToArray等)来应用select表达式,否则Entity Framework将无法生成正确的select语句,它将始终选择表中的所有字段。

答案 3 :(得分:0)

第一个.ToList()不是必需的。

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

您是否要求结果集为List&lt;&gt;?或者只是IEnumerable或ICollection足够吗?如果是这样,则可能不需要最后一个.ToList()。

你问过性能?您希望每个查询返回多少个实例?如果它相对较少,则.ToList()或.ToArray()或其他不会产生任何有意义的差异。它更多的是您需要公开哪种功能?如果返回的对象需要是可索引的,可添加的,并且具有List的其他属性,那没关系。但是,如果您所做的只是迭代返回的集合,请不要暴露不需要的内容。