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<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端执行。