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

C# IEnumerable/IQueryable扩展的奇怪行为(没有延迟加载?),c#,linq,entity-framework,lazy-loading,C#,Linq,Entity Framework,Lazy Loading,我有一些奇怪的行为我搞不懂 根据我的理解(这显然是错误的),以下两种方法在给定IQueryable时的行为应该是相同的,但它们不是 如果使用IQueryable调用第一个(对象是实体框架中显式用作IQueryable的DbSet),它看起来不使用延迟加载(它对数据库执行扫描)。当我用同一个对象调用第二个方法时,它似乎按照我希望的方式工作(它对数据库执行搜索) 因此,有两个问题: 为什么会这样 我能(以及如何)使最通用的方法(使用IEnumerable)正常工作吗?(因为我有更多的扩展,不想重复

我有一些奇怪的行为我搞不懂

根据我的理解(这显然是错误的),以下两种方法在给定IQueryable时的行为应该是相同的,但它们不是

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

因此,有两个问题:

  • 为什么会这样

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

我正在使用EF4.1处理SQLServerExpress 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);
}
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扩展方法的工作方式与您希望的不同。