IEnumerable / IQueryable扩展的奇怪行为(没有延迟加载?)

时间:2011-07-27 18:20:48

标签: c# linq entity-framework lazy-loading

我有一些奇怪的行为,我无法弄明白。

以下两种方法应该从我理解的内容(显然是错误的)的行为与给定IQueryable的方式相同,但它们没有。

如果我用IQueryable调用第一个(对象是实体框架中显式使用的DbSet作为IQueryable),它看起来好像不使用延迟加载(它在数据库上执行扫描)。当我使用相同的对象调用第二个方法时,它似乎按照我想要的方式工作(它在数据库上执行搜索)。

所以,有两个问题:

  • 为什么会这样?

  • 我(和如何)可以使最通用的方法(使用IEnumerable)“正常”工作吗? (因为我有更多的扩展,不想重复代码,我想避免重载,只需复制粘贴方法体,如下所示)

我正在使用EF 4.1对抗SQL Server Express 2008数据库

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, long id) where TEntity : Identifiable
{
   return list.SingleOrDefault(e => e.ID == id);
}

public static TEntity GetByID<TEntity>(this IQueryable<TEntity> list, long id) where TEntity : Identifiable
{
    return list.SingleOrDefault(e => e.ID == id);
}

4 个答案:

答案 0 :(得分:5)

IEnumerable<T>不会将查询表达式传递给EF LINQ提供程序,而是在内存中执行SingleOrDefault()。这需要将表完全加载到内存中,然后是SingleOrDefault()。通过使用IQueryable<T>版本,提供程序将获得正确的表达式树,并将其转换为所需的SQL。这就是差异的来源。

答案 1 :(得分:2)

由于list在第一种情况下被输入为IEnumerable,因此实体框架查询提供程序将此作为提示在内存上执行SingleOrDefault()方法 (它将使用Enumerable上的Linq方法而不是Queryable) - 这就是为什么你看到数据库扫描实现完整列表的原因。

另请参阅AsEnumerable()方法和Jon Skeet撰写的这篇文章:"Reimplementing LINQ to Objects: Part 36 - AsEnumerable"

答案 2 :(得分:0)

FirstOrDefault是针对IEnumerable和IQueryable单独定义的 在第二种情况下调用System.Linq.Queryable.FirstOrDefault()。

如果要将两者合并为一个方法,则可以测试list是否为IQueryable,而是使用Queryable扩展方法。

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, long id) where TEntity : Identifiable
{
    var query = list as IQueryable<TEntity>;
    if (query != null)
        return query.SingleOrDefault(e => e.ID == id);
    return list.SingleOrDefault(e => e.ID == id);
}

答案 3 :(得分:0)

对于任何来到这里的人,还有一件事需要注意:

您需要绝对确定要为SingleOrDefault()方法提供表达式,否则可能会发生以下情况:

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, Func<TEntity,bool> selector) where TEntity : Identifiable
{
   return list.SingleOrDefault(selector);
}

public static TEntity GetByID<TEntity>(this IQueryable<TEntity> list, Func<TEntity,bool> selector) where TEntity : Identifiable
{
    return list.SingleOrDefault(selector);
}

这些方法将完全相同。为什么?因为您没有向IQueryable<>列表提供表达式,所以会有效地将列表自动转换为IEnumerable<>,然后选择器将在IEnumerable<>。因此,实际上,您实际上是将整个表/列表从数据库中拉入内存,然后在内存中的列表中运行选择器。

我刚刚完成了这件事,我虽然疯了。

如果您将Expression<Func<TEntity,bool>>传递给IQueryable<>,则会执行数据库端。