使用IEnumerable和IQueryable作为ObjectSet类型时的区别

7

据我所知,当我在IQueryable上使用LINQ扩展方法(使用lambda表达式语法),而该IQueryable实际上是ObjectSet的实例时,它们会被翻译成LINQ to SQL查询。我的意思是,命令

IQueryable<User> users = db.UserSet;
var users32YearsOld = users.Where(user => user.Age == 32);

与...完全相同

IQueryable<User> users = db.UserSet;   
var users32YearsOld = from user in users where user.Age == 32 select user;

因此,在循环中枚举users32YearsOld之类的操作之前,它们都没有命中数据库(希望我理解得正确)。

但是,如果我不将ObjectSet掩码为IQueryable而是掩码为IEnumerable会发生什么? 所以如果它的类型是IEnumerable

IEnumerable<User> users = db.UserSet;
var users32YearsOld = users.Where(user => user.Age == 32);

这个操作会立即影响数据库吗(如果是,那么是在什么时候?是在第一行还是第二行?)还是像之前的命令一样,只有在枚举 users32YearsOld 时才会影响数据库?如果我使用以下命令是否会有任何区别?

IEnumerable<User> users = db.UserSet;
var users32YearsOld = from user in users where user.Age == 32 select user;

谢谢你

4个回答

12

我撤销了对我的答案的删除,因为我刚刚测试了它,并且它正好按照我描述的方式工作:

所提到的任何查询都不会命中数据库,因为没有枚举。 IQueryable 查询和 IEnumerable 查询之间的区别在于,在 IQueryable 的情况下,过滤将在数据库服务器上执行,而在 IEnumerable 的情况下,所有对象将从数据库加载到内存中,并且过滤将在 .NET 代码(linq-to-objects)中完成。正如您可以想象的那样,这通常会导致性能问题。

我在我的项目中编写了一个简单的测试:

[TestMethod]
public void Test()
{
    // ObjectQuery<Department> converted ot IEnumerable<Department>
    IEnumerable<Department> departmetns = CreateUnitOfWork().GetRepository<Department>().GetQuery();
    // No query execution here - Enumerable has also deffered exection
    var query = departmetns.Where(d => d.Id == 1); 
    // Queries ALL DEPARTMENTS here and executes First on the retrieved result set
    var result = departmetns.First(); 
}

之前提到的查询都不会命中数据库,因为没有枚举,这与前两个答案相矛盾。迄今为止,我从你那里得到了非常好的答案,所以我倾向于更认真地考虑你的答案。仍然有“没有枚举”吗?当在IEnumerable上调用Where(u => u.Age == 32)时,它不会进行枚举吗?这是如何实现的(我可以想象它的工作方式类似于IQueryable,但知道比猜测更好)? - Rasto
1
我进行了测试并恢复了我的答案,因为在使用SQL Profiler检查后,我发现我的答案是正确的。 - Ladislav Mrnka
就像我说的,这是我第一次认真对待它...所以当我在某些IEnumerable上调用Where(u => u.Age == 32)时,它不会枚举,而只是创建一个新的可枚举对象,该对象具有对原始对象的引用和一些“where”过滤器附加?或者它是如何工作的?也许这应该是另一个问题... - Rasto
我无法解释它的工作原理,但对我来说是有意义的(IEnumerableIQueryable都具有延迟执行)。这就是为什么在测试之前我对我的答案产生了怀疑的原因。 - Ladislav Mrnka
据我所知,当在 IQueryable 变量上进行 .Where() 或其他 Linq 调用时,表示表达式的对象树将被添加到实现 IQueryable 的对象中。IQueryable 实现了 IEnumerable,因此同样的事情也会发生在这里。当你实际枚举表达式时,表达式树将被评估/转换为 SQL 并传递到数据库服务器。 - Tony Vitabile

6
这里有一个简单的解释:
IEnumerable<User> usersEnumerable = db.UserSet;
IQueryable<User> usersQueryable = db.UserSet;

var users = /* one of usersEnumerable or usersQueryable */;

var age32StartsWithG = users.Where(user => user.Age == 32)
                            .Where(user => user.Name.StartsWith("G");

如果您使用usersEnumerable,当您开始枚举它时,两个Where将按顺序运行;首先,ObjectSet将获取所有对象,然后将对象过滤为32岁的对象,然后再将这些对象过滤为以G开头的对象。
如果您使用usersQueryable,则两个Where将返回新对象,这些对象将累积选择条件,并在开始枚举时将所有条件转换为查询。这会产生明显的差异。
通常,您不需要担心,因为在声明变量时,您要么说var users,要么说ObjectSet users,这意味着C#将知道您有兴趣调用ObjectSet上可用的最具体的方法,而IQueryable查询运算符方法(WhereSelect等)比IEnumerable方法更具体。但是,如果您将对象传递给接受IEnumerable参数的方法,则可能会调用错误的方法。
您还可以利用这种工作方式,通过使用AsEnumerable()AsQueryable()方法来开始使用其他方法。例如,var groupedPeople = users.Where(user => user.Age > 15).AsEnumerable().GroupBy(user => user.Age);将使用数据库查询获取正确的用户,然后在本地对对象进行分组。
正如其他人所说,值得重申的是,在开始枚举序列(使用foreach)之前,不会发生任何事情。您现在应该理解为什么不能有其他方式:如果所有结果都一次检索,您无法构建要转换为更高效查询(例如SQL查询)的查询。

1

IEnumerable和IQueryable的区别在于,如果有多个筛选条件,IEnumerable会一个接着一个地执行这些筛选。例如,从100个元素中输出20个元素通过第一个筛选器,然后再使用第二个筛选器来筛选所需的10个元素。它将向数据库发出一次查询,但会下载不必要的数据。而使用IQueryable将再次进行一次查询,但只会下载所需的10项数据。以下链接提供了一些关于这些查询如何工作的优秀示例: https://filteredcode.wordpress.com/2016/04/29/ienumerable-vs-iqueryable-part-2-practical-questions/


0

关于IQueryable你说得对。至于IEnumerable,只要给IEnumerable用户分配了,它就会立即访问数据库。

在使用示例中,使用Linq扩展和语法之间没有真正的区别。有时一个比另一个更方便(请参见linq-extension-methods-vs-linq-syntax),但依我看,这更多取决于个人喜好。


所以当使用 IEnumerable 时,它会立即连接数据库,并请求用户表中的所有行或仅设置年龄为32岁的行吗? - Rasto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接