Sử dụng IQueryable với Linq


256

Việc sử dụng IQueryabletrong bối cảnh của LINQ là gì?

Nó được sử dụng để phát triển các phương pháp mở rộng hoặc bất kỳ mục đích nào khác?

Câu trả lời:


507

Câu trả lời của Marc Gravell rất đầy đủ, nhưng tôi nghĩ tôi cũng muốn thêm điều gì đó từ quan điểm của người dùng, ...


Sự khác biệt chính, từ quan điểm của người dùng, là, khi bạn sử dụng IQueryable<T>(với một nhà cung cấp hỗ trợ mọi thứ một cách chính xác), bạn có thể tiết kiệm rất nhiều tài nguyên.

Ví dụ: nếu bạn đang làm việc với cơ sở dữ liệu từ xa, với nhiều hệ thống ORM, bạn có tùy chọn tìm nạp dữ liệu từ một bảng theo hai cách, một trả về IEnumerable<T>và một trả về một IQueryable<T>. Ví dụ: bạn có bảng Sản phẩm và bạn muốn nhận tất cả các sản phẩm có chi phí> $ 25.

Nếu bạn làm:

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

Điều xảy ra ở đây, là cơ sở dữ liệu tải tất cả các sản phẩm và chuyển chúng qua dây cho chương trình của bạn. Chương trình của bạn sau đó lọc dữ liệu. Về bản chất, cơ sở dữ liệu thực hiện SELECT * FROM Productsvà trả về MỌI sản phẩm cho bạn.

Với IQueryable<T>nhà cung cấp phù hợp , mặt khác, bạn có thể làm:

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

Mã trông giống nhau, nhưng sự khác biệt ở đây là SQL được thực thi SELECT * FROM Products WHERE Cost >= 25.

Từ POV của bạn là một nhà phát triển, điều này trông giống nhau. Tuy nhiên, từ quan điểm hiệu suất, bạn chỉ có thể trả lại 2 bản ghi trên mạng thay vì 20.000 ....


9
Định nghĩa của "GetQueryable Products ();" ở đâu?
Pankaj

12
@StackOverflowUser Dự định là bất kỳ phương thức nào trả về một IQueryable<Product>- sẽ dành riêng cho ORM hoặc kho lưu trữ của bạn, v.v.
Reed Copsey

đúng. nhưng bạn đã đề cập đến mệnh đề where sau hàm này gọi. Vì vậy, hệ thống vẫn không biết về bộ lọc. Tôi có nghĩa là nó vẫn lấy tất cả các hồ sơ của sản phẩm. đúng?
Pankaj

@StackOverflowUser Không - đó là vẻ đẹp của IQueryable <T> - nó có thể được thiết lập để đánh giá khi bạn nhận được kết quả - có nghĩa là mệnh đề Where, được sử dụng sau thực tế, vẫn sẽ được dịch sang câu lệnh SQL chạy trên máy chủ và chỉ kéo các yếu tố cần thiết trên dây ...
Reed Copsey

40
@Testing Bạn thực sự vẫn chưa đến DB. Cho đến khi bạn thực sự liệt kê kết quả (ví dụ: sử dụng a foreachhoặc gọi ToList()), bạn không thực sự nhấn DB.
Sậy Copsey

188

Về bản chất, công việc của nó rất giống với IEnumerable<T>- đại diện cho một nguồn dữ liệu Queryablecó thể truy vấn - sự khác biệt là các phương thức LINQ khác nhau (bật ) có thể cụ thể hơn, để xây dựng truy vấn bằng cách sử dụng Expressioncây thay vì đại biểu (đó là cách Enumerablesử dụng).

Các cây biểu thức có thể được kiểm tra bởi nhà cung cấp LINQ đã chọn của bạn và biến thành một truy vấn thực tế - mặc dù bản thân đó là một nghệ thuật đen.

Điều này thực sự không phù hợp ElementType, ExpressionProvider- nhưng trong thực tế, bạn hiếm khi cần quan tâm đến điều này như một người dùng . Chỉ người thực hiện LINQ cần biết các chi tiết chính.


Nhận xét lại; Tôi không chắc chắn những gì bạn muốn bằng ví dụ, nhưng hãy xem xét LINQ-to-SQL; đối tượng trung tâm ở đây là một DataContext, đại diện cho trình bao bọc cơ sở dữ liệu của chúng tôi. Điều này thường có một thuộc tính trên mỗi bảng (ví dụ Customers:) và một bảng thực hiện IQueryable<Customer>. Nhưng chúng ta không sử dụng nó trực tiếp; xem xét:

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);
    }
}

điều này trở thành (bởi trình biên dịch C #):

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

một lần nữa được giải thích (bởi trình biên dịch C #) là:

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

Điều quan trọng là, các phương thức tĩnh trên các Queryablecây biểu thức, mà - chứ không phải IL thông thường, được biên dịch thành một mô hình đối tượng. Ví dụ: chỉ cần nhìn vào "Ở đâu", điều này cho chúng ta một cái gì đó tương đương với:

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) ...

Không phải trình biên dịch đã làm rất nhiều cho chúng ta? Mô hình đối tượng này có thể được xé ra, kiểm tra ý nghĩa của nó và đặt lại với nhau bởi trình tạo TSQL - đưa ra một cái gì đó như:

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

(chuỗi có thể kết thúc như một tham số; tôi không thể nhớ)

Không ai có thể làm điều này nếu chúng ta vừa sử dụng một đại biểu. Và đây là điểm của Queryable/ IQueryable<T>: nó cung cấp điểm vào để sử dụng cây biểu thức.

Tất cả điều này là rất phức tạp, vì vậy đó là một công việc tốt mà trình biên dịch làm cho nó tốt đẹp và dễ dàng cho chúng tôi.

Để biết thêm thông tin, hãy xem " C # in Depth " hoặc " LINQ in Action ", cả hai đều cung cấp bảo hiểm cho các chủ đề này.


2
Nếu bạn không phiền, bạn có thể cập nhật cho tôi ví dụ đơn giản dễ hiểu (nếu bạn có thời gian).
user190560

Bạn có thể giải thích "Định nghĩa của" GetQueryable Products () ở đâu; "?" trong bài trả lời của ông Reed Copsey
Pankaj

Rất thích dòng dịch biểu thức cho một truy vấn là "nghệ thuật đen của riêng mình" ... rất nhiều sự thật đó
afreeland

Tại sao không ai có thể làm được nếu bạn đã sử dụng một đại biểu?
David Klempfner

1
@Backwards_Dave vì một điểm ủy nhiệm (về cơ bản) cho IL và IL không đủ sức biểu cảm để làm cho hợp lý khi cố gắng giải mã ý định đủ để xây dựng SQL. IL cũng cho phép quá nhiều thứ - tức là hầu hết mọi thứ có thể diễn đạt trong IL không thể được thể hiện bằng cú pháp giới hạn rằng việc chuyển thành những thứ như SQL
Marc Gravell

17

Mặc dù Reed CopseyMarc Gravell đã mô tả về IQueryable(và cũng IEnumerable) đủ, nhưng tôi muốn thêm một chút nữa ở đây bằng cách cung cấp một ví dụ nhỏ trên IQueryableIEnumerablenhư nhiều người dùng đã yêu cầu

Ví dụ : Tôi đã tạo hai bảng trong cơ sở dữ liệu

   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)

Khóa chính ( PersonId) của bảng Employeecũng là khóa forgein ( personid) của bảngPerson

Tiếp theo tôi đã thêm mô hình thực thể ado.net trong ứng dụng của mình và tạo bên dưới lớp dịch vụ trên đó

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;
    }
}

chúng chứa cùng linq. Nó được gọi program.csnhư được định nghĩa dưới đây

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();
    }
}

Đầu ra là như nhau cho cả hai rõ ràng

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

Vậy câu hỏi là sự khác biệt / ở đâu? Nó dường như không có sự khác biệt nào phải không? Có thật không!!

Chúng ta hãy xem các truy vấn sql được tạo và thực hiện bởi framwork 5 thực thể trong khoảng thời gian này

Phần thực thi 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]

Phần thực thi vô số

--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)

Kịch bản chung cho cả hai phần thực thi

/* 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
*/

Vì vậy, bây giờ bạn có một vài câu hỏi, hãy để tôi đoán chúng và cố gắng trả lời chúng

Tại sao các tập lệnh khác nhau được tạo ra cho cùng một kết quả?

Hãy tìm hiểu một số điểm ở đây,

tất cả các truy vấn có một phần chung

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

tại sao? Bởi vì cả hai chức năng IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerablecác SomeServiceClasschứa một dòng phổ biến trong các truy vấn LINQ

where employeesToCollect.Contains(e.PersonId)

Hơn tại sao AND (N'M' = [Extent1].[Gender])phần bị thiếu trong IEnumerablephần thực thi, trong khi trong cả hai chức năng gọi, chúng tôi đã sử dụng Where(i => i.Gender == "M") inchương trình.cs`

Bây giờ chúng ta đang ở điểm khác biệt giữa IQueryableIEnumerable

Framwork thực thể làm gì khi một IQueryablephương thức được gọi, nó lấy câu lệnh linq được viết bên trong phương thức và cố gắng tìm hiểu xem có nhiều biểu thức linq được xác định trên tập kết quả hay không, sau đó tập hợp tất cả các truy vấn linq được xác định cho đến khi kết quả cần tìm nạp và xây dựng sql phù hợp hơn truy vấn để thực hiện.

Nó cung cấp rất nhiều lợi ích như,

  • chỉ những hàng được điền bởi máy chủ sql có thể hợp lệ bởi toàn bộ thực thi truy vấn linq
  • giúp hiệu suất máy chủ sql bằng cách không chọn các hàng không cần thiết
  • giảm chi phí mạng

như ở đây trong ví dụ máy chủ sql chỉ trả về ứng dụng hai hàng sau khi thực thi IQueryable` nhưng trả về BA hàng cho truy vấn IEnumerable tại sao?

Trong trường hợp IEnumerablephương thức, khung thực thể lấy câu lệnh linq được viết bên trong phương thức và xây dựng truy vấn sql khi kết quả cần tìm nạp. nó không bao gồm phần linq còn lại để xây dựng truy vấn sql. Giống như ở đây không lọc được thực hiện trong máy chủ sql trên cột gender.

Nhưng đầu ra có giống nhau không? Bởi vì 'IEnumerable lọc kết quả hơn nữa ở cấp ứng dụng sau khi truy xuất kết quả từ máy chủ sql

Vậy, ai đó nên chọn cái gì? Cá nhân tôi thích xác định kết quả hàm IQueryable<T>vì có rất nhiều lợi ích mà nó mang lại IEnumerable, bạn có thể tham gia hai hoặc nhiều hàm IQueryable, tạo ra tập lệnh cụ thể hơn cho máy chủ sql.

Ở đây trong ví dụ, bạn có thể thấy một IQueryable Query(IQueryableQuery2)tập lệnh tạo ra một kịch bản cụ thể hơn so với IEnumerable query(IEnumerableQuery2)quan điểm của tôi.


2

Nó cho phép truy vấn sâu hơn nữa xuống dòng. Nếu điều này vượt ra ngoài ranh giới dịch vụ, thì người dùng của đối tượng IQueryable này sẽ được phép làm nhiều hơn với nó.

Chẳng hạn, nếu bạn đang sử dụng tải lười biếng với nhibernate thì điều này có thể dẫn đến biểu đồ được tải khi / nếu cần.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.