简体   繁体   English

将IQueryable与Linq一起使用

[英]Using IQueryable with Linq

What is the use of IQueryable in the context of LINQ? 在LINQ的上下文中IQueryable什么用?

Is it used for developing extension methods or any other purpose? 它是用于开发扩展方法还是任何其他目的?

Marc Gravell's answer is very complete, but I thought I'd add something about this from the user's point of view, as well... Marc Gravell的答案非常完整,但我想我会从用户的角度添加一些内容,以及......


The main difference, from a user's perspective, is that, when you use IQueryable<T> (with a provider that supports things correctly), you can save a lot of resources. 从用户的角度来看,主要区别在于,当您使用IQueryable<T> (使用支持正确的提供程序)时,您可以节省大量资源。

For example, if you're working against a remote database, with many ORM systems, you have the option of fetching data from a table in two ways, one which returns IEnumerable<T> , and one which returns an IQueryable<T> . 例如,如果您正在使用许多ORM系统对抗远程数据库,则可以选择以两种方式从表中获取数据,一种返回IEnumerable<T> ,另一种返回IQueryable<T> Say, for example, you have a Products table, and you want to get all of the products whose cost is >$25. 比如说,您有一个Products表,并且您希望获得成本> 25美元的所有产品。

If you do: 如果你这样做:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

What happens here, is the database loads all of the products, and passes them across the wire to your program. 这里发生的是数据库加载所有产品,并将它们通过网络传递给您的程序。 Your program then filters the data. 然后,您的程序会过滤数据。 In essence, the database does a SELECT * FROM Products , and returns EVERY product to you. 本质上,数据库执行SELECT * FROM Products ,并将每个产品返回给您。

With the right IQueryable<T> provider, on the other hand, you can do: 另一方面,使用正确的IQueryable<T>提供程序,您可以:

 IQueryable<Product> products = myORM.GetQueryableProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

The code looks the same, but the difference here is that the SQL executed will be SELECT * FROM Products WHERE Cost >= 25 . 代码看起来一样,但不同之处在于执行的SQL将是SELECT * FROM Products WHERE Cost >= 25

From your POV as a developer, this looks the same. 从您的POV作为开发人员,这看起来是一样的。 However, from a performance standpoint, you may only return 2 records across the network instead of 20,000.... 但是,从性能角度来看,您只能在网络上返回2条记录而不是20,000条....

In essence its job is very similar to IEnumerable<T> - to represent a queryable data source - the difference being that the various LINQ methods (on Queryable ) can be more specific, to build the query using Expression trees rather than delegates (which is what Enumerable uses). 本质上它的工作与IEnumerable<T>非常相似 - 表示可查询的数据源 - 区别在于各种LINQ方法(在Queryable )可以更具体,使用Expression树而不是委托来构建查询(这是Enumerable用途)。

The expression trees can be inspected by your chosen LINQ provider and turned into an actual query - although that is a black art in itself. 表达式树可以由您选择的LINQ提供程序进行检查,并转换为实际查询 - 尽管这本身就是一种黑色艺术。

This is really down to the ElementType , Expression and Provider - but in reality you rarely need to care about this as a user . 这实际上ElementTypeExpressionProvider - 但实际上你很少需要关心这个用户 Only a LINQ implementer needs to know the gory details. 只有LINQ 实现者需要了解血腥细节。


Re comments; 评论; I'm not quite sure what you want by way of example, but consider LINQ-to-SQL; 我不太确定你想要的例子,但考虑LINQ-to-SQL; the central object here is a DataContext , which represents our database-wrapper. 这里的中心对象是一个DataContext ,它代表我们的数据库包装器。 This typically has a property per table (for example, Customers ), and a table implements IQueryable<Customer> . 这通常具有每个表的属性(例如, Customers ),并且表实现IQueryable<Customer> But we don't use that much directly; 但我们并没有直接使用那么多; consider: 考虑:

using(var ctx = new MyDataContext()) {
    var qry = from cust in ctx.Customers
              where cust.Region == "North"
              select new { cust.Id, cust.Name };
    foreach(var row in qry) {
        Console.WriteLine("{0}: {1}", row.Id, row.Name);
    }
}

this becomes (by the C# compiler): 这变成了(通过C#编译器):

var qry = ctx.Customers.Where(cust => cust.Region == "North")
                .Select(cust => new { cust.Id, cust.Name });

which is again interpreted (by the C# compiler) as: 再次解释(由C#编译器):

var qry = Queryable.Select(
              Queryable.Where(
                  ctx.Customers,
                  cust => cust.Region == "North"),
              cust => new { cust.Id, cust.Name });

Importantly, the static methods on Queryable take expression trees, which - rather than regular IL, get compiled to an object model. 重要的是, Queryable上的静态方法采用表达式树,而不是常规的IL,它被编译为对象模型。 For example - just looking at the "Where", this gives us something comparable to: 例如 - 只看“Where”,这给我们提供了类似于:

var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
                  Expression.Equal(
                      Expression.Property(cust, "Region"),
                      Expression.Constant("North")
                  ), cust);

... Queryable.Where(ctx.Customers, lambda) ...

Didn't the compiler do a lot for us? 编译器没有为我们做很多事吗? This object model can be torn apart, inspected for what it means, and put back together again by the TSQL generator - giving something like: 这个对象模型可以拆开,检查它的含义,然后通过TSQL生成器重新组合在一起 - 给出类似的东西:

 SELECT c.Id, c.Name
 FROM [dbo].[Customer] c
 WHERE c.Region = 'North'

(the string might end up as a parameter; I can't remember) (字符串可能最终作为参数;我不记得了)

None of this would be possible if we had just used a delegate. 如果我们刚刚使用了一个代表,这一切都不可能。 And this is the point of Queryable / IQueryable<T> : it provides the entry-point for using expression trees. 就是Queryable / IQueryable<T>的要点:它提供了使用表达式树的入口点。

All this is very complex, so it is a good job that the compiler makes it nice and easy for us. 所有这一切都非常复杂,所以编译器使它对我们来说很容易也很好。

For more information, look at " C# in Depth " or " LINQ in Action ", both of which provide coverage of these topics. 有关更多信息,请查看“ C#in Depth ”或“ LINQ in Action ”,它们都提供了这些主题的内容。

Although Reed Copsey and Marc Gravell already described about IQueryable (and also IEnumerable ) enough,mI want to add little more here by providing a small example on IQueryable and IEnumerable as many users asked for it 虽然Reed CopseyMarc Gravell已经足够描述了IQueryable (以及IEnumerable ),但是我希望通过提供一个关于IQueryableIEnumerable的小例子来增加更多,因为很多用户都要求它

Example : I have created two table in database 示例 :我在数据库中创建了两个表

   CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
   CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)

The Primary key( PersonId ) of table Employee is also a forgein key( personid ) of table Person Employee的主键( PersonId )也是表Person的forgein键( personid

Next i added ado.net entity model in my application and create below service class on that 接下来,我在我的应用程序中添加了ado.net实体模型,并在其上创建了以下服务类

public class SomeServiceClass
{   
    public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }

    public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }
}

they contains same linq. 它们包含相同的linq。 It called in program.cs as defined below 它在program.cs调用如下定义

class Program
{
    static void Main(string[] args)
    {
        SomeServiceClass s= new SomeServiceClass(); 

        var employeesToCollect= new []{0,1,2,3};

        //IQueryable execution part
        var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");            
        foreach (var emp in IQueryableList)
        {
            System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());

        //IEnumerable execution part
        var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
        foreach (var emp in IEnumerableList)
        {
           System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());

        Console.ReadKey();
    }
}

The output is same for both obviously 显然,两者的输出相同

ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IQueryable contain 2 row in result set  
ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IEnumerable contain 2 row in result set

So the question is what/where is the difference? 所以问题是差异在哪里/哪里? It does not seem to have any difference right? 它似乎没有任何区别对吗? Really!! 真!!

Let's have a look on sql queries generated and executed by entity framwork 5 during these period 让我们来看看在这段时间内由实体框架5生成和执行的sql查询

IQueryable execution part IQueryable执行部分

--IQueryableQuery1 
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])

--IQueryableQuery2
SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Employee] AS [Extent1]
    WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
)  AS [GroupBy1]

IEnumerable execution part IEnumerable执行部分

--IEnumerableQuery1
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

--IEnumerableQuery2
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

Common script for both execution part 两个执行部分的通用脚本

/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
   Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1 
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

--ICommonQuery2
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/

So you have few questions now, let me guess those and try to answer them 所以你现在几乎没有问题,让我猜那些并试着回答它们

Why are different scripts generated for same result? 为什么为同一结果生成不同的脚本?

Lets find out some points here, 让我们在这里找到一些观点,

all queries has one common part 所有查询都有一个共同点

WHERE [Extent1].[PersonId] IN (0,1,2,3)

why? 为什么? Because both function IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable and IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable of SomeServiceClass contains one common line in linq queries 因为函数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable SomeServiceClass在linq查询中包含一个公共行

where employeesToCollect.Contains(e.PersonId)

Than why is the AND (N'M' = [Extent1].[Gender]) part is missing in IEnumerable execution part, while in both function calling we used Where(i => i.Gender == "M") in program.cs` 为什么在IEnumerable执行部分中缺少AND (N'M' = [Extent1].[Gender])部分,而在函数调用中我们Where(i => i.Gender == "M") in程序中使用Where(i => i.Gender == "M") in .cs`

Now we are in the point where difference came between IQueryable and IEnumerable 现在我们处于IQueryableIEnumerable之间的差异

What entity framwork does when an IQueryable method called, it tooks linq statement written inside the method and try to find out if more linq expressions are defined on the resultset, it then gathers all linq queries defined until the result need to fetch and constructs more appropriate sql query to execute. IQueryable方法调用时,它执行了什么实体框架,它在方法内部编写了linq语句,并试图找出是否在结果集上定义了更多的linq表达式,然后收集所有定义的linq查询,直到结果需要获取并构造更合适sql查询执行。

It provide a lots of benefits like, 它提供了很多好处,比如

  • only those rows populated by sql server which could be valid by the whole linq query execution 只有那些由sql server填充的行,这些行可能由整个linq查询执行有效
  • helps sql server performance by not selecting unnecessary rows 通过不选择不必要的行来帮助sql server性能
  • network cost get reduce 网络成本降低

like here in example sql server returned to application only two rows after IQueryable execution` but returned THREE rows for IEnumerable query why? 像在这里例如SQL服务器返回给应用程序的IQueryable execution`后只有两行,但返回的排为IEnumerable的查询,为什么?

In case of IEnumerable method, entity framework took linq statement written inside the method and constructs sql query when result need to fetch. IEnumerable方法的情况下,实体框架在方法内部编写了linq语句,并在需要获取结果时构造sql查询。 it does not include rest linq part to constructs the sql query. 它不包括rest linq部分来构造sql查询。 Like here no filtering is done in sql server on column gender . 像这里没有在列gender SQL服务器上进行过滤。

But the outputs are same? 但输出是一样的吗? Because 'IEnumerable filters the result further in application level after retrieving result from sql server 因为'IEnumerable在从sql server检索结果后在应用程序级别进一步过滤结果

SO, what should someone choose? 那么,究竟应该有人选择呢? I personally prefer to define function result as IQueryable<T> because there are lots of benefit it has over IEnumerable like, you could join two or more IQueryable functions, which generate more specific script to sql server. 我个人更喜欢将函数结果定义为IQueryable<T>因为它比IEnumerable有很多好处,你可以加入两个或多个IQueryable函数,它们为sql server生成更具体的脚本。

Here in example you can see an IQueryable Query(IQueryableQuery2) generates a more specific script than IEnumerable query(IEnumerableQuery2) which is much more acceptable in my point of view. 在示例中,您可以看到IQueryable Query(IQueryableQuery2)生成比IEnumerable query(IEnumerableQuery2)更具体的脚本,这在我看来更容易接受。

It allows for further querying further down the line. 它允许进一步查询下线。 If this was beyond a service boundary say, then the user of this IQueryable object would be allowed to do more with it. 如果这超出了服务边界,那么这个IQueryable对象的用户将被允许使用它做更多的事情。

For instance if you were using lazy loading with nhibernate this might result in graph being loaded when/if needed. 例如,如果您使用延迟加载和nhibernate,这可能会导致在/如果需要时加载图形。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM