IQueryable,List,IEnumerator之间的区别?


68

我想知道IQueryable,List,IEnumerator和什么时候应该使用每一个之间有什么区别?

例如,当使用Linq到SQL时,我将执行以下操作:

public List<User> GetUsers()
{
   return db.User.where(/* some query here */).ToList();
}

现在,我想知道是否应该使用IQueryable。我不确定在列表中使用它的好处。

Answers:


123

IQueryable<T>旨在允许查询提供程序(例如,诸如LINQ to SQL或Entity Framework之类的ORM)使用查询中包含的表达式将请求转换为另一种格式。换句话说,LINQ-to-SQL会查看您正在使用的实体的属性以及进行的比较,并实际上创建一个SQL语句来表达(希望)一个等效请求。

IEnumerable<T>IQueryable<T>(尽管IQueryable<T>Implement的所有实例IEnumerable<T>)更通用,并且仅定义一个序列。但是,Enumerable该类中有可用的扩展方法,这些扩展方法在该接口上定义了一些查询类型的运算符,并使用普通的代码来评估这些条件。

List<T>只是一种输出格式,虽然实现了IEnumerable<T>,但与查询没有直接关系。

换句话说,当您使用时IQueryable<T>,您正在定义一个表达式,该表达式将转换为其他形式。即使您正在编写代码,该代码也永远不会被执行,只会被检查并转化为其他内容,例如实际的SQL查询。因此,在这些表达式中只有某些事情有效。例如,您不能调用从这些表达式中定义的普通函数,因为LINQ-to-SQL不知道如何将您的调用转换为SQL语句。不幸的是,这些限制中的大多数仅在运行时评估。

IEnumerable<T>用于查询时,您正在使用LINQ-to-Objects,这意味着您正在编写用于评估查询或转换结果的实际代码,因此,一般而言,您不能做任何限制。您可以从这些表达式中自由调用其他函数。

使用LINQ to SQL

与上面的区别紧密联系在一起,牢记这在实践中如何工作也很重要。当您在LINQ to SQL中针对数据上下文类编写查询时,它将生成一个IQueryable<T>。无论您IQueryable<T>自身采取什么措施,都将变成SQL,因此您的过滤和转换将在服务器上完成。不管你做什么反对这种作为IEnumerable<T>,将在应用层来完成。有时这是理想的(例如,在需要使用客户端代码的情况下),但是在许多情况下,这是无意的。

例如,如果我有一个带有Customers表示Customer表的属性的上下文,并且每个客户都有一CustomerId列,那么让我们看一下执行此查询的两种方法:

var query = (from c in db.Customers where c.CustomerId == 5 select c).First();

这将产生SQL,该SQL向数据库查询等于5的Customer记录CustomerId。类似:

select CustomerId, FirstName, LastName from Customer where CustomerId = 5

现在,如果使用扩展方法Customers转换为,会发生什么?IEnumerable<Customer>AsEnumerable()

var query = (from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c).First();

这种简单的变化具有严重的后果。由于我们要转换CustomersIEnumerable<Customer>,这将带回整个表格并在客户端对其进行过滤(嗯,严格来讲,这将带回表格中的每一行,直到遇到符合条件的那一行为止,但重点是相同)。

ToList()

到目前为止,我们仅讨论了IQueryableIEnumerable。这是因为它们是相似的互补界面。在这两种情况下,您都将定义一个查询;也就是说,您正在定义要在哪里找到数据,要应用哪些过滤器以及要返回什么数据。这两个都是查询

query = from c in db.Customers where c.CustomerId == 5 select c;
query = from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c;

就像我们之前讨论的那样,第一个查询正在使用IQueryable,第二个查询正在使用IEnumerable。但是,在这两种情况下,这都只是一个query。定义查询实际上对数据源没有任何作用。当代码开始遍历列表时,实际上会执行查询。这可以通过多种方式发生。一个foreach循环,调用ToList()等。

该查询在第一次每次迭代时执行。如果你要调用ToList()query两倍,你最终会得到两个清单完全不同的对象。它们可能包含相同的数据,但是它们将是不同的引用。

评论后编辑

我只想弄清楚何时在客户端完成事情和何时在服务器端完成事情之间的区别。如果您将an引用IQueryable<T>IEnumerable<T>,则只有之后进行的查询IEnumerable<T>才会在客户端进行。例如,假设我有此表和LINQ-to-SQL上下文:

Customer
-----------
CustomerId
FirstName
LastName

我首先基于构建查询FirstName。这将创建一个IQueryable<Customer>

var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c;

现在,我将该查询传递给采用的函数,IEnumerable<Customer>并根据进行一些过滤LastName

public void DoStuff(IEnumerable<Customer> customers)
{
    foreach(var cust in from c in customers where c.LastName.StartsWith("Ro"))
    {
        Console.WriteLine(cust.CustomerId);
    }
}

我们在这里进行了第二次查询,但是正在对进行查询IEnumerable<Customer>。这里将要发生的是将运行此SQL对第一个查询进行评估:

select CustomerId, FirstName, LastName from Customer where FirstName like 'Ad%'

因此,我们将带回所有FirstName以开头的人"Ad"。请注意,这里没有任何内容LastName。那是因为它被客户端过滤掉了。

一旦返回这些结果,程序便会遍历结果并仅传递以LastName开头的记录"Ro"。这样做的缺点是,我们带回来的数据-即,所有行,其LastName 下手"Ro"凹口-可能已被过滤掉的服务器上。


@Adam Robinson-更好的做法是允许他们获取仍未命中数据库的返回查询,并且当他们需要列表时,可以遍历它(然后命中db源)。还是只给他们一份蝙蝠清单更好?
chobo2 2011年

1
@ chobo2:这取决于您要执行的操作,因此那里没有一个正确的答案。在某些情况下,允许用户进一步细化查询可能是有利的,尤其是在您可能具有多个不同查询共有的复杂查询逻辑的情况下。虽然这并不是说您应该始终这样做,但是IQueryable使用基本的复杂逻辑返回后,您可以在不同的地方重用该逻辑,而无需在代码中重复使用
Adam Robinson

@Adam Robinson-在这种情况下会发生什么。我返回一个IQueryable列表,但是我的视图需要使用IEnumerator。我将其转换为Enumerable会涉及到某些问题吗?
chobo2 2011年

@ chobo2:IEnumerator还是IEnumerable?如果您将的实例传递IQueryable<T>给采用的函数,IEnumerable<T>就可以了,但是在该函数中完成的所有查询都将在客户端完成(除非将其强制转换回IQueryable<T>,当然,但是它应该使用IQueryable<T>if它将会这样做)。如果期望的话IEnumerator<T>,那就是完全不同的接口。
亚当·罗宾逊

亚当·罗宾逊(Adam Robinson)-嗯,我没有意识到我认为其中2个在哪里。如果它在客户端执行所有查询,那就不好了。我正在尝试做的是将一个视图模型发送到包含要通过的IEnumerable的视图。
chobo2 2011年

10

IQueryable<T>:抽象数据库访问,支持惰性查询查询
List<T>:条目的集合。不支持惰性评估
IEnumerator<T>:提供遍历and和IEnumerable<T>IQueryable<T>and and List<T>are)的能力

该代码的问题非常简单-它总是在被调用时执行查询。如果要返回db.User.Where(...)(是IQueryable<T>),则将保留查询的评估,直到实际需要它为止(重复)。同样,如果该方法的用户需要指定其他谓词,则这些谓词也将在数据库中执行,这使其速度更快。


您还说什么进一步的谓词,说它们在执行您的where子句而不影响数据库?那么,如果他们需要说对它进行更进一步的过滤,就可以不打分贝,直到对它进行迭代,然后再这样做吗?
chobo2 2011年

2
通常,IQueryable<T>只有迭代一次,才会进行评估。现在,如果将另一个链接Where(...)到它,那也将在数据库中执行。使用您的代码,它将在内存中进行评估。数据库旨在执行此类任务,并且比内存过滤要快。
Femaref 2011年

1

当您想要某个实体的强类型集合时,请使用iListList<item>

和使用IqueryableIenumurator当您想要将哑数据作为对象的集合时,它将作为松散的类型集合返回,并且没有任何限制。

我宁愿使用,List<type>因为使用列表包装并在强类型集合中强制转换了我的结果集。

另外,使用列表将使您能够添加,排序和转换图层为Array,Ienumurator或可查询的图层。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.