我有一些奇怪的行为,我无法弄明白。
以下两种方法应该从我理解的内容(显然是错误的)的行为与给定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);
}
答案 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<>
,则会执行数据库端。