C# IEnumerable/IQueryable扩展的奇怪行为(没有延迟加载?)
我有一些奇怪的行为我搞不懂 根据我的理解(这显然是错误的),以下两种方法在给定IQueryable时的行为应该是相同的,但它们不是 如果使用IQueryable调用第一个(对象是实体框架中显式用作IQueryable的DbSet),它看起来不使用延迟加载(它对数据库执行扫描)。当我用同一个对象调用第二个方法时,它似乎按照我希望的方式工作(它对数据库执行搜索) 因此,有两个问题:C# IEnumerable/IQueryable扩展的奇怪行为(没有延迟加载?),c#,linq,entity-framework,lazy-loading,C#,Linq,Entity Framework,Lazy Loading,我有一些奇怪的行为我搞不懂 根据我的理解(这显然是错误的),以下两种方法在给定IQueryable时的行为应该是相同的,但它们不是 如果使用IQueryable调用第一个(对象是实体框架中显式用作IQueryable的DbSet),它看起来不使用延迟加载(它对数据库执行扫描)。当我用同一个对象调用第二个方法时,它似乎按照我希望的方式工作(它对数据库执行搜索) 因此,有两个问题: 为什么会这样 我能(以及如何)使最通用的方法(使用IEnumerable)正常工作吗?(因为我有更多的扩展,不想重复
- 为什么会这样
- 我能(以及如何)使最通用的方法(使用IEnumerable)正常工作吗?(因为我有更多的扩展,不想重复代码,所以我希望避免重载,只需复制粘贴方法体,如下所示)
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);
}
publicstatictenty-GetByID(这个IEnumerable列表,长id),其中tenty:Identification
{
返回list.SingleOrDefault(e=>e.ID==ID);
}
公共静态TEntity GetByID(此IQueryable列表,长id),其中TEntity:可识别
{
返回list.SingleOrDefault(e=>e.ID==ID);
}
由于在第一种情况下,列表
被键入为IEnumerable
,实体框架查询提供程序将此作为提示,在内存中对其执行SingleOrDefault()
方法(它将使用可枚举
上的Linq方法,而不是可查询
)-这就是您看到数据库扫描以实现完整列表的原因
另请参见Jon Skeet的方法和本文:
IEnumerable
不会将查询表达式传递给EF LINQ提供程序,而是在内存中执行SingleOrDefault()。这需要将表完全加载到内存中,然后是SingleOrDefault()
。通过使用IQueryable
版本,提供程序将获得正确的表达式树,并将其转换为所需的SQL。这就是区别的来源。第一个默认值是分别为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);
}
publicstatictenty-GetByID(这个IEnumerable列表,长id),其中tenty:Identification
{
var query=列表为IQueryable;
if(查询!=null)
返回query.SingleOrDefault(e=>e.ID==ID);
返回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);
}
publicstatictenty-GetByID(此IEnumerable列表,Func选择器),其中tenty:Identifiable
{
返回列表。SingleOrDefault(选择器);
}
公共静态TEntity GetByID(此IQueryable列表,Func选择器),其中TEntity:可识别
{
返回列表。SingleOrDefault(选择器);
}
这些方法的运行方式完全相同。为什么?由于您没有向IQueryable
列表提供表达式,因此实际情况是列表将自动转换为IEnumerable
,然后在IEnumerable
上运行选择器。因此,实际上,您实际上是将整个表/列表从数据库拉入内存,然后在内存中的列表上运行选择器
我刚做完这件事,我觉得我快疯了
如果将表达式
传递到IQueryable
,它将在DB端执行。正确。SingleOrDefault是接口上的扩展方法。因此IEnumerable的实现与IQueryable完全不同。区别的线索是接口的名称:IEnumerable枚举集合(并且必须让整个集合都枚举),IQueryable使用表达式(就像查询一样)。IEnumerable扩展方法的工作方式与您希望的不同。