2011-07-27 199 views
2

我有一些奇怪的行为,我找不出来。IEnumerable/IQueryable扩展的奇怪行为(没有延迟加载?)

下面的两个方法应该从我的理解(这显然是错误的)给予IQueryable的行为相同,但他们不这样做。

如果我用IQueryable调用第一个对象(该对象是显式用作IQueryable的实体框架中的DbSet),它看起来像不使用延迟加载(它对数据库执行扫描)。当我用同一个对象调用第二个方法时,它看起来像我想要的那样工作(它在数据库上执行查找)。

于是,两个问题:

  • 这究竟是为什么?

  • 我可以(以及如何)使最通用的方法(与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

IEnumerable扩展方法无法按照您希望的方式工作。 – hatchet

回答

5

IEnumerable<T>未通过查询表达式的EF LINQ提供,而是执行在存储器中的SingleOrDefault()。这需要您的表格完整载入内存,然后是SingleOrDefault()。通过使用IQueryable<T>版本,提供者将被赋予正确的表达式树,并将其转换为您想要的SQL。这就是差异所在。

+0

对。 SingleOrDefault是接口上的扩展方法。所以IEnumerable的实现与IQueryable完全不同。区别的线索是接口的名称:IEnumerable枚举集合(并且必须获取整个集合来完成此操作),IQueryable使用表达式(如查询)。 – hatchet

2

由于list是你的第一个案例类型为IEnumerable实体框架查询供应商将其视为暗示在内存上执行SingleOrDefault()方法(它会使用LINQ的方法上Enumerable代替Queryable) - 这就是为什么你看到数据库扫描实现完整列表。

还请参阅AsEnumerable()方法,并且此发布者乔恩斯基特:"Reimplementing LINQ to Objects: Part 36 - AsEnumerable"

0

FirstOrDefault单独为IEnumerable和IQueryable定义 在第二种情况下调用System.Linq.Queryable.FirstOrDefault()。

如果您想将两者结合到一个方法中,您可以测试列表是否为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); 
} 
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<>,它将在DB端执行。