Trả lại IEn Countable <T> so với IQueryable <T>


1085

Sự khác biệt giữa trở về IQueryable<T>so với IEnumerable<T>khi nào nên được ưu tiên hơn so với cái khác?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

Câu trả lời:


1778

Vâng, cả hai sẽ cung cấp cho bạn thực hiện hoãn lại .

Sự khác biệt là IQueryable<T>giao diện cho phép LINQ-to-SQL (LINQ.-to-anything thực sự) hoạt động. Vì vậy, nếu bạn tiếp tục tinh chỉnh truy vấn của mình trên một IQueryable<T>, truy vấn đó sẽ được thực hiện trong cơ sở dữ liệu, nếu có thể.

Trong IEnumerable<T>trường hợp, nó sẽ là LINQ-to-object, nghĩa là tất cả các đối tượng phù hợp với truy vấn ban đầu sẽ phải được tải vào bộ nhớ từ cơ sở dữ liệu.

Trong mã:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Mã đó sẽ thực thi SQL để chỉ chọn khách hàng vàng. Mặt khác, đoạn mã sau sẽ thực hiện truy vấn ban đầu trong cơ sở dữ liệu, sau đó lọc ra các khách hàng không phải là vàng trong bộ nhớ:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Đây là một sự khác biệt khá quan trọng và IQueryable<T>trong nhiều trường hợp , việc này có thể giúp bạn tránh trả lại quá nhiều hàng từ cơ sở dữ liệu. Một ví dụ điển hình khác là thực hiện phân trang: Nếu bạn sử dụng TakeSkipbật IQueryable, bạn sẽ chỉ nhận được số lượng hàng được yêu cầu; làm điều đó trên IEnumerable<T>sẽ khiến tất cả các hàng của bạn được tải trong bộ nhớ.


32
Giải thích tuyệt vời. Có bất kỳ tình huống nào mà IEnumerable sẽ thích hợp hơn với IQueryable không?
fjxx

8
Vì vậy, chúng ta có thể nói rằng nếu chúng ta đang sử dụng IQueryable để truy vấn Đối tượng bộ nhớ, thì chúng sẽ không có sự khác biệt nào giữa IEnumerable và IQueryable?
Tarik

11
CẢNH BÁO: Mặc dù IQueryable có thể là một giải pháp hấp dẫn do tối ưu hóa đã nêu, nhưng không được phép vượt qua kho lưu trữ hoặc lớp dịch vụ. Điều này là để bảo vệ cơ sở dữ liệu của bạn khỏi chi phí gây ra bởi "xếp chồng các biểu thức LINQ".
Yorro

48
@fjxx Vâng. Nếu bạn muốn lọc lặp lại trên kết quả ban đầu của bạn (một số kết quả cuối cùng). Làm điều đó trên giao diện IQueryable sẽ tạo ra một số vòng tròn cho cơ sở dữ liệu, khi thực hiện nó trên IEnumerable sẽ thực hiện việc lọc trong bộ nhớ, làm cho nó nhanh hơn (trừ khi lượng dữ liệu là HUGE)
Per Hornshøj-Schierbeck

34
Một lý do khác để thích IEnumerableđến IQueryablelà không phải tất cả các hoạt động LINQ được hỗ trợ bởi tất cả các nhà cung cấp LINQ. Vì vậy, miễn là bạn biết bạn đang làm gì, bạn có thể sử dụng IQueryableđể đẩy càng nhiều truy vấn đến nhà cung cấp LINQ (LINQ2Query, EF, NHibernate, MongoDB, v.v.). Nhưng nếu bạn để mã khác làm bất cứ điều gì nó muốn thì IQueryablecuối cùng bạn sẽ gặp rắc rối vì một số mã máy khách ở đâu đó đã sử dụng thao tác không được hỗ trợ. Tôi đồng ý với khuyến nghị không phát hành IQueryables "vào tự nhiên" qua kho lưu trữ hoặc lớp tương đương.
Avish 2/214

302

Câu trả lời hàng đầu là tốt nhưng nó không đề cập đến các cây biểu thức giải thích "cách" hai giao diện khác nhau. Về cơ bản, có hai bộ phần mở rộng LINQ giống hệt nhau. Where(), Sum(), Count(), FirstOrDefault(), Vv tất cả đều có hai phiên bản: một chấp nhận chức năng và một trong đó chấp nhận biểu thức.

  • Các IEnumerablechữ ký phiên bản là:Where(Func<Customer, bool> predicate)

  • Các IQueryablechữ ký phiên bản là:Where(Expression<Func<Customer, bool>> predicate)

Có lẽ bạn đã sử dụng cả hai thứ đó mà không nhận ra vì cả hai đều được gọi bằng cú pháp giống hệt nhau:

ví dụ: Where(x => x.City == "<City>")hoạt động trên cả IEnumerableIQueryable

  • Khi sử dụng Where()trên một IEnumerablebộ sưu tập, trình biên dịch chuyển một hàm được biên dịch sangWhere()

  • Khi sử dụng Where()trên một IQueryablebộ sưu tập, trình biên dịch chuyển một cây biểu thức đến Where(). Một cây biểu thức giống như hệ thống phản chiếu nhưng cho mã. Trình biên dịch chuyển đổi mã của bạn thành một cấu trúc dữ liệu mô tả những gì mã của bạn làm theo định dạng dễ tiêu hóa.

Tại sao phải bận tâm với điều cây biểu hiện này? Tôi chỉ muốn Where()lọc dữ liệu của tôi. Lý do chính là cả ORM của EF và Linq2Query đều có thể chuyển đổi cây biểu thức trực tiếp thành SQL, nơi mã của bạn sẽ thực thi nhanh hơn nhiều.

Ồ, nghe có vẻ như tăng hiệu suất miễn phí, tôi có nên sử dụng AsQueryable()mọi nơi trong trường hợp đó không? Không, IQueryablechỉ hữu ích nếu nhà cung cấp dữ liệu cơ bản có thể làm gì đó với nó. Chuyển đổi một cái gì đó như thường xuyên Listsang IQueryablesẽ không mang lại cho bạn bất kỳ lợi ích nào.


9
IMO nó tốt hơn câu trả lời được chấp nhận. Tuy nhiên, tôi không nhận được một điều: IQueryable không mang lại lợi ích gì cho các đối tượng thông thường, OK, nhưng nó có tệ hơn theo bất kỳ cách nào không? Bởi vì nếu nó không mang lại bất kỳ lợi ích nào, thì nó không đủ lý do để thích IEnumerable, vì vậy ý ​​tưởng sử dụng IQueryable ở mọi nơi vẫn còn hiệu lực.
Sergei Tachenov

1
Sergey, IQueryable mở rộng IEnumerable vì vậy khi sử dụng IQueryable, bạn tải nhiều hơn vào bộ nhớ so với việc khởi tạo IEnumerable! Vì vậy, đây là một đối số. ( stackoverflow.com/questions/12064828/ đá c ++ mặc dù tôi nghĩ rằng tôi có thể ngoại suy điều này)
Viking

Đồng ý với Sergei về việc đây là câu trả lời tốt nhất (mặc dù câu trả lời được chấp nhận là tốt). Tôi muốn nói thêm rằng theo kinh nghiệm của tôi, IQueryablekhông phân tích chức năng cũng như IEnumerable: chẳng hạn, nếu bạn muốn biết những yếu tố DBSet<BookEntity>nào không có trong một List<BookObject>, dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))sẽ ném ra một ngoại lệ : Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'. Tôi đã phải thêm .ToList()sau dbSetObject.
Jean-David Lanz

80

Có, cả hai sử dụng thực hiện hoãn lại. Hãy minh họa sự khác biệt bằng cách sử dụng trình cấu hình SQL Server ....

Khi chúng tôi chạy đoạn mã sau:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

Trong SQL Server profiler, chúng tôi tìm thấy một lệnh bằng:

"SELECT * FROM [dbo].[WebLog]"

Mất khoảng 90 giây để chạy khối mã đó với bảng WebLog có 1 triệu bản ghi.

Vì vậy, tất cả các bản ghi bảng được tải vào bộ nhớ dưới dạng các đối tượng và sau đó với mỗi .Where () nó sẽ là một bộ lọc khác trong bộ nhớ đối với các đối tượng này.

Khi chúng ta sử dụng IQueryablethay vì IEnumerabletrong ví dụ trên (dòng thứ hai):

Trong SQL Server profiler, chúng tôi tìm thấy một lệnh bằng:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Mất khoảng bốn giây để chạy khối mã này bằng cách sử dụng IQueryable.

IQueryable có một thuộc tính được gọi là Expressionlưu trữ một biểu thức cây bắt đầu được tạo khi chúng ta sử dụng resulttrong ví dụ của chúng ta (được gọi là thực thi hoãn lại) và cuối cùng, biểu thức này sẽ được chuyển đổi thành truy vấn SQL để chạy trên công cụ cơ sở dữ liệu.


5
Điều này dạy tôi khi truyền tới IEnumerable, IQueryable bên dưới sẽ mất phương thức mở rộng IQueryable.
Ngáp

56

Cả hai sẽ cho bạn thực hiện hoãn lại, vâng.

Đối với cái được ưu tiên hơn cái khác, nó phụ thuộc vào nguồn dữ liệu cơ bản của bạn là gì.

Trả lại một IEnumerablesẽ tự động buộc thời gian chạy sử dụng LINQ to Object để truy vấn bộ sưu tập của bạn.

Trả lại một IQueryable(mà thực hiện IEnumerable, bằng cách này) cung cấp chức năng bổ sung để dịch truy vấn của bạn thành thứ gì đó có thể hoạt động tốt hơn trên nguồn cơ bản (LINQ sang SQL, LINQ sang XML, v.v.).


30

Nói chung, tôi muốn giới thiệu như sau:

  • Trả về IQueryable<T>nếu bạn muốn cho phép nhà phát triển sử dụng phương thức của mình để tinh chỉnh truy vấn bạn trả về trước khi thực hiện.

  • Quay trở lại IEnumerablenếu bạn muốn vận chuyển một tập hợp các Đối tượng để liệt kê.

Hãy tưởng tượng IQueryablenhư nó là gì - một "truy vấn" cho dữ liệu (mà bạn có thể tinh chỉnh nếu bạn muốn). An IEnumerablelà một tập hợp các đối tượng (đã được nhận hoặc đã được tạo) mà bạn có thể liệt kê.


2
"Có thể liệt kê", không "có thể IEnumerable."
Casey

28

Rất nhiều điều đã được nói trước đây, nhưng trở lại gốc rễ, theo một cách kỹ thuật hơn:

  1. IEnumerable là một tập hợp các đối tượng trong bộ nhớ mà bạn có thể liệt kê - một chuỗi trong bộ nhớ cho phép lặp lại (làm cho nó dễ dàng trong foreachvòng lặp, mặc dù bạn chỉ có thể đi theo IEnumerator). Họ cư trú trong bộ nhớ như là.
  2. IQueryable là một cây biểu thức sẽ được dịch sang một thứ khác vào một lúc nào đó với khả năng liệt kê kết quả cuối cùng . Tôi đoán đây là điều khiến hầu hết mọi người bối rối.

Họ rõ ràng có ý nghĩa khác nhau.

IQueryableđại diện cho một cây biểu thức (một truy vấn, đơn giản) sẽ được dịch sang một thứ khác bởi nhà cung cấp truy vấn cơ bản ngay khi các API phát hành được gọi, như các hàm tổng hợp LINQ (Sum, Count, v.v.) hoặc ToList [Array, Dictionary ,. ..]. Và IQueryablecác đối tượng cũng thực hiện IEnumerable, IEnumerable<T>để nếu chúng đại diện cho một truy vấn , kết quả của truy vấn đó có thể được lặp lại. Điều đó có nghĩa là IQueryable không phải là truy vấn duy nhất. Thuật ngữ đúng là chúng là cây biểu hiện .

Bây giờ làm thế nào những biểu thức đó được thực thi và những gì chúng chuyển sang tất cả được gọi là nhà cung cấp truy vấn (người thực hiện biểu thức mà chúng ta có thể nghĩ về chúng).

Trong thế giới Entity Framework (là nhà cung cấp nguồn dữ liệu cơ bản bí ẩn hoặc nhà cung cấp truy vấn) IQueryableđược dịch thành các truy vấn T-SQL nguyên gốc . Nhibernatelàm những điều tương tự với họ. Ví dụ, bạn có thể viết một khái niệm của riêng mình theo các khái niệm được mô tả khá rõ trong LINQ: Xây dựng liên kết Nhà cung cấp IQueryable và bạn có thể muốn có API truy vấn tùy chỉnh cho dịch vụ nhà cung cấp cửa hàng sản phẩm của mình.

Về cơ bản, IQueryablecác đối tượng sẽ được xây dựng suốt thời gian dài cho đến khi chúng tôi giải phóng chúng một cách rõ ràng và yêu cầu hệ thống viết lại chúng thành SQL hoặc bất cứ điều gì và gửi chuỗi thực thi để xử lý tiếp theo.

Như thể trì hoãn thực thi, đó là một LINQtính năng để giữ sơ đồ cây biểu thức trong bộ nhớ và chỉ gửi nó vào thực thi theo yêu cầu, bất cứ khi nào các API nhất định được gọi theo trình tự (cùng Count, ToList, v.v.).

Việc sử dụng hợp lý cả hai phụ thuộc rất nhiều vào các nhiệm vụ bạn phải đối mặt trong trường hợp cụ thể. Đối với mẫu kho lưu trữ nổi tiếng mà cá nhân tôi chọn để trả về IList, đó là IEnumerabletrên Danh sách (bộ chỉ mục và tương tự). Vì vậy, lời khuyên của tôi là IQueryablechỉ sử dụng trong các kho lưu trữ và IEnumerable ở bất kỳ nơi nào khác trong mã. Không nói về mối quan tâm kiểm tra mà IQueryablephá vỡ và phá hỏng nguyên tắc tách mối quan tâm . Nếu bạn trả lại một biểu thức từ trong kho, người tiêu dùng có thể chơi với lớp kiên trì như họ muốn.

Một bổ sung nhỏ cho mớ hỗn độn :) (từ một cuộc thảo luận trong các bình luận)) Không ai trong số họ là đối tượng trong bộ nhớ vì chúng không phải là loại thực sự, chúng là điểm đánh dấu của một loại - nếu bạn muốn đi sâu. Nhưng nó có ý nghĩa (và đó là lý do tại sao ngay cả MSDN cũng hiểu theo cách này) khi nghĩ về IEnumerables như các bộ sưu tập trong bộ nhớ trong khi IQueryables là cây biểu thức. Vấn đề là giao diện IQueryable kế thừa giao diện IEnumerable để nếu nó đại diện cho một truy vấn, kết quả của truy vấn đó có thể được liệt kê. Phép liệt kê làm cho cây biểu thức được liên kết với một đối tượng IQueryable được thực thi. Vì vậy, trên thực tế, bạn không thể thực sự gọi bất kỳ thành viên IEn nào mà không có đối tượng trong bộ nhớ. Nó sẽ vào trong đó nếu bạn làm, dù sao đi nữa, nếu nó không trống. IQueryables chỉ là truy vấn, không phải dữ liệu.


3
Nhận xét rằng IEnumerables luôn trong bộ nhớ không thực sự đúng. Giao diện IQueryable thực hiện giao diện IEnumerable. Vì điều này, bạn có thể chuyển một IQueryable thô đại diện cho truy vấn LINQ-to-SQL ngay vào một khung nhìn mong đợi một IEnumerable! Bạn có thể ngạc nhiên khi thấy rằng bối cảnh dữ liệu của bạn đã hết hạn hoặc cuối cùng bạn gặp vấn đề với MARS (nhiều bộ kết quả hoạt động).

vì vậy, trên thực tế, bạn thực sự không thể gọi bất kỳ thành viên IEn nào mà không có đối tượng trong bộ nhớ. Nó sẽ vào trong đó nếu bạn làm, dù sao đi nữa, nếu nó không trống. IQueryables chỉ là truy vấn, không phải dữ liệu. Nhưng tôi thực sự thấy quan điểm của bạn. Tôi sẽ thêm một bình luận về điều này.
Arman McHitarian 4/2/2015

@AlexanderPritchard không ai trong số họ là đối tượng trong bộ nhớ vì chúng không phải là loại thực, chúng là điểm đánh dấu của một loại - nếu bạn muốn đi sâu. Nhưng nó có ý nghĩa (và đó là lý do tại sao ngay cả MSDN cũng hiểu theo cách này) khi nghĩ về IEnumerables như các bộ sưu tập trong bộ nhớ trong khi IQueryables là cây biểu thức. Vấn đề là giao diện IQueryable kế thừa giao diện IEnumerable để nếu nó đại diện cho một truy vấn, kết quả của truy vấn đó có thể được liệt kê. Phép liệt kê làm cho cây biểu thức được liên kết với một đối tượng IQueryable được thực thi.
Arman McHitarian 4/2/2015

24

Nói chung, bạn muốn duy trì kiểu tĩnh ban đầu của truy vấn cho đến khi nó quan trọng.

Vì lý do này, bạn có thể xác định biến của mình là 'var' thay vì IQueryable<>hoặcIEnumerable<> bạn sẽ biết rằng bạn không thay đổi loại.

Nếu bạn bắt đầu với một IQueryable<>, bạn thường muốn giữ nó như một IQueryable<>cho đến khi có một số lý do thuyết phục để thay đổi nó. Lý do cho điều này là bạn muốn cung cấp cho bộ xử lý truy vấn càng nhiều thông tin càng tốt. Ví dụ: nếu bạn chỉ sử dụng 10 kết quả (bạn đã gọi Take(10)) thì bạn muốn SQL Server biết về điều đó để nó có thể tối ưu hóa các gói truy vấn của nó và chỉ gửi cho bạn dữ liệu bạn sẽ sử dụng.

Một lý do thuyết phục để thay đổi kiểu từ IQueryable<>để IEnumerable<>có thể là bạn đang gọi một số chức năng mở rộng rằng việc thực hiện IQueryable<>trong đối tượng cụ thể của bạn, hoặc không thể xử lý hoặc xử lý không hiệu quả. Trong trường hợp đó, bạn có thể muốn chuyển đổi loại thành IEnumerable<>(bằng cách gán cho một biến loại IEnumerable<>hoặc bằng cách sử dụng AsEnumerablephương thức tiện ích mở rộng) để các hàm mở rộng mà bạn gọi cuối cùng là loại trong Enumerablelớp thay vì Queryablelớp.


18

Có một bài đăng blog với mẫu mã nguồn ngắn gọn về việc lạm dụng IEnumerable<T>có thể ảnh hưởng đáng kể đến hiệu suất truy vấn LINQ: Entity Framework: IQueryable so với IEnumerable .

Nếu chúng ta đào sâu hơn và xem xét các nguồn, chúng ta có thể thấy rằng rõ ràng có các phương thức mở rộng khác nhau được sử dụng cho IEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

Cái đầu tiên trả về iterator có thể đếm được và cái thứ hai tạo truy vấn thông qua nhà cung cấp truy vấn, được chỉ định trong IQueryablenguồn.


11

Gần đây tôi gặp vấn đề với IEnumerablev IQueryable. Thuật toán được sử dụng trước tiên thực hiện một IQueryabletruy vấn để thu được một tập hợp kết quả. Những thứ này sau đó được chuyển đến một foreachvòng lặp, với các mục được khởi tạo thành một lớp Entity Framework (EF). Lớp EF này sau đó đã được sử dụng trong frommệnh đề của truy vấn Linq to Entity, khiến kết quả là IEnumerable.

Tôi khá mới đối với EF và Linq cho các Thực thể, vì vậy phải mất một thời gian để tìm ra nút thắt cổ chai là gì. Sử dụng MiniProfiling, tôi tìm thấy truy vấn và sau đó chuyển đổi tất cả các hoạt động riêng lẻ thành một IQueryabletruy vấn Linq cho Thực thể duy nhất . Các IEnumerablemất 15 giây và IQueryablemất 0,5 giây để thực thi. Có ba bảng liên quan và sau khi đọc nó, tôi tin rằngIEnumerable truy vấn thực sự tạo thành một sản phẩm chéo ba bảng và lọc kết quả.

Cố gắng sử dụng IQueryables làm quy tắc và lập hồ sơ công việc của bạn để làm cho các thay đổi của bạn có thể đo lường được.


lý do là các biểu thức IQueryable được chuyển đổi thành SQL nguyên gốc trong EF và được thực thi ngay trong DB trong khi danh sách IEnumerable là các đối tượng trong bộ nhớ. Chúng được tìm nạp từ DB tại một số điểm khi bạn gọi các hàm tổng hợp như Count, Sum hoặc bất kỳ To ... và hoạt động trong bộ nhớ sau đó. IQueryableChúng cũng bị kẹt trong bộ nhớ khi bạn đã gọi một trong các API đó, nhưng nếu không, bạn có thể chuyển biểu thức lên ngăn xếp của các lớp và chơi xung quanh với các bộ lọc cho đến khi gọi API. DAL được thiết kế tốt như một Kho lưu trữ được thiết kế tốt sẽ giải quyết loại vấn đề này;)
Arman McHitarian

10

Tôi muốn làm rõ một vài điều do các phản ứng dường như mâu thuẫn (chủ yếu xung quanh IEnumerable).

(1) IQueryablemở rộng IEnumerablegiao diện. (Bạn có thể gửi một IQueryablecái gì đó mà IEnumerablekhông mong đợi .)

(2) Cả IQueryableIEnumerableLINQ đều cố tải lười biếng khi lặp qua tập kết quả. (Lưu ý rằng việc triển khai có thể được nhìn thấy trong các phương thức mở rộng giao diện cho từng loại.)

Nói cách khác, IEnumerableskhông chỉ riêng "trong bộ nhớ". IQueryableskhông phải luôn luôn được thực hiện trên cơ sở dữ liệu. IEnumerablephải tải mọi thứ vào bộ nhớ (một khi được truy xuất, có thể là lười biếng) vì nó không có nhà cung cấp dữ liệu trừu tượng. IQueryablesdựa vào một nhà cung cấp trừu tượng (như LINQ-to-SQL), mặc dù đây cũng có thể là nhà cung cấp .NET trong bộ nhớ.

Trường hợp sử dụng mẫu

(a) Truy xuất danh sách các bản ghi IQueryabletừ bối cảnh EF. (Không có bản ghi nào trong bộ nhớ.)

(b) Truyền IQueryablecho một khung nhìn có mô hình IEnumerable. (Hợp lệ. IQueryableKéo dài IEnumerable.)

(c) Lặp lại và truy cập các bản ghi, thực thể con và thuộc tính của tập dữ liệu từ dạng xem. (Có thể gây ra ngoại lệ!)

Các vấn đề có thể xảy ra

(1) Các IEnumerablenỗ lực tải lười biếng và bối cảnh dữ liệu của bạn đã hết hạn. Ngoại lệ ném vì nhà cung cấp không còn có sẵn.

(2) Proxy thực thể của Entity Framework được bật (mặc định) và bạn cố gắng truy cập một đối tượng (ảo) có liên quan với bối cảnh dữ liệu đã hết hạn. Tương tự như (1).

(3) Nhiều bộ kết quả hoạt động (MARS). Nếu bạn đang lặp lại IEnumerabletrong một foreach( var record in resultSet )khối và đồng thời cố gắng truy cậprecord.childEntity.childProperty , bạn có thể kết thúc bằng MARS do lười tải cả bộ dữ liệu và thực thể quan hệ. Điều này sẽ gây ra một ngoại lệ nếu nó không được kích hoạt trong chuỗi kết nối của bạn.

Giải pháp

  • Tôi đã thấy rằng việc kích hoạt MARS trong chuỗi kết nối hoạt động không đáng tin cậy. Tôi đề nghị bạn tránh MARS trừ khi nó được hiểu rõ và mong muốn rõ ràng.

Thực hiện truy vấn và lưu trữ kết quả bằng cách gọi resultList = resultSet.ToList() Đây dường như là cách đơn giản nhất để đảm bảo các thực thể của bạn nằm trong bộ nhớ.

Trong trường hợp bạn đang truy cập các thực thể liên quan, bạn vẫn có thể yêu cầu bối cảnh dữ liệu. Hoặc, hoặc bạn có thể vô hiệu hóa proxy thực thể và Includethực thể liên quan rõ ràng từ của bạn DbSet.


9

Sự khác biệt chính giữa các IE IEnumerable và và IQueryable trực tiếp là về nơi logic bộ lọc được thực thi. Một thực thi ở phía máy khách (trong bộ nhớ) và cái còn lại thực thi trên cơ sở dữ liệu.

Ví dụ: chúng ta có thể xem xét một ví dụ về việc chúng ta có 10.000 bản ghi cho một người dùng trong cơ sở dữ liệu của mình và giả sử chỉ có 900 người dùng đang hoạt động, vì vậy trong trường hợp này, nếu chúng ta sử dụng thì IE IEnumerable, sau đó, nó sẽ tải tất cả 10.000 bản ghi vào bộ nhớ và sau đó áp dụng bộ lọc IsActive trên đó để trả về 900 người dùng đang hoạt động.

Mặt khác, trong trường hợp tương tự, nếu chúng ta sử dụng, IQueryable, thì nó sẽ áp dụng trực tiếp bộ lọc IsActive trên cơ sở dữ liệu trực tiếp từ đó sẽ trả về 900 người dùng đang hoạt động.

Liên kết tham khảo


cái nào được tối ưu hóa và trọng lượng nhẹ về hiệu suất?
Sitecore Sam

@Sam "IQueryable" được ưa thích hơn về mặt tối ưu hóa và trọng lượng nhẹ.
Tabish Usman

6

Chúng ta có thể sử dụng cả hai cho cùng một cách, và chúng chỉ khác nhau trong hiệu suất.

IQueryable chỉ thực thi đối với cơ sở dữ liệu một cách hiệu quả. Nó có nghĩa là nó tạo ra toàn bộ truy vấn chọn và chỉ nhận được các bản ghi liên quan.

Ví dụ: chúng tôi muốn đưa 10 khách hàng hàng đầu có tên bắt đầu bằng 'Nimal'. Trong trường hợp này, truy vấn chọn sẽ được tạo như select top 10 * from Customer where name like ‘Nimal%’.

Nhưng nếu chúng tôi sử dụng IEnumerable, truy vấn sẽ như thế nào select * from Customer where name like ‘Nimal%’và mười câu hỏi hàng đầu sẽ được lọc ở cấp mã hóa C # (nó lấy tất cả các bản ghi của khách hàng từ cơ sở dữ liệu và chuyển chúng vào C #).


5

Ngoài 2 câu trả lời thực sự tốt đầu tiên (bởi Driis & by Jacob):

Giao diện IEnumerable nằm trong không gian tên System.Collections.

Đối tượng IEnumerable đại diện cho một tập hợp dữ liệu trong bộ nhớ và chỉ có thể di chuyển trên dữ liệu này. Truy vấn được đại diện bởi đối tượng IEnumerable được thực thi ngay lập tức và hoàn toàn, vì vậy ứng dụng nhận dữ liệu nhanh chóng.

Khi truy vấn được thực thi, IEnumerable tải tất cả dữ liệu và nếu chúng ta cần lọc nó, việc lọc sẽ được thực hiện ở phía máy khách.

Giao diện IQueryable nằm trong không gian tên System.Linq.

Đối tượng IQueryable cung cấp quyền truy cập từ xa vào cơ sở dữ liệu và cho phép bạn điều hướng qua dữ liệu theo thứ tự trực tiếp từ đầu đến cuối hoặc theo thứ tự ngược lại. Trong quá trình tạo truy vấn, đối tượng được trả về là IQueryable, truy vấn được tối ưu hóa. Kết quả là, bộ nhớ được tiêu thụ ít hơn trong quá trình thực thi, băng thông mạng ít hơn, nhưng đồng thời nó có thể được xử lý chậm hơn một chút so với truy vấn trả về một đối tượng IEnumerable.

Chọn cái gì?

Nếu bạn cần toàn bộ tập hợp dữ liệu được trả về, thì tốt hơn là sử dụng IEnumerable, cung cấp tốc độ tối đa.

Nếu bạn KHÔNG cần toàn bộ tập hợp dữ liệu được trả về, nhưng chỉ một số dữ liệu được lọc, thì tốt hơn là sử dụng IQueryable.


0

Ngoài những điều trên, thật thú vị khi lưu ý rằng bạn có thể có ngoại lệ nếu bạn sử dụng IQueryablethay vì IEnumerable:

Sau đây hoạt động tốt nếu productsIEnumerable:

products.Skip(-4);

Tuy nhiên, nếu productsIQueryablevà nó đang cố truy cập các bản ghi từ bảng DB, thì bạn sẽ gặp lỗi này:

Phần bù được chỉ định trong mệnh đề OFFSET có thể không âm.

Điều này là do các truy vấn sau đây đã được xây dựng:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

và OFFSET không thể có giá trị âm.

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.