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

本文关键字:延迟加载 IQueryable 扩展 IEnumerable | 更新日期: 2023-09-27 18:03:58

我有一些奇怪的行为,我不明白。

根据我的理解(这显然是错误的),下面的两个方法应该以相同的方式给定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);
}

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

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

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

参见AsEnumerable()方法和Jon Skeet的这篇文章:"重新实现LINQ到对象:第36部分- AsEnumerable"

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

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

您需要绝对确定您为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<>上运行。因此,实际上,您实际上是从DB中将整个表/列表拉到内存中,然后在内存中的列表上运行选择器。

我刚做完这个,我以为我要疯了。

如果您将Expression<Func<TEntity,bool>>传递给IQueryable<>,它将在DB端执行。