2010-08-02 29 views
40

xem xét mã này:Tôi có hiểu nhầm LINQ to SQL .AsEnumerable()?

var query = db.Table 
       .Where(t => SomeCondition(t)) 
       .AsEnumerable(); 

int recordCount = query.Count(); 
int totalSomeNumber = query.Sum(); 
decimal average = query.Average(); 

Giả query mất một thời gian rất dài để chạy. Tôi cần tính số lượng bản ghi, tổng số tiền trả về là SomeNumber và mất trung bình vào cuối. Tôi nghĩ dựa trên đọc của tôi rằng .AsEnumerable() sẽ thực hiện truy vấn bằng LINQ-to-SQL, sau đó sử dụng LINQ-to-Objects cho Count, SumAverage. Thay vào đó, khi tôi làm điều này trong LINQPad, tôi thấy cùng một truy vấn được chạy ba lần. Nếu tôi thay thế .AsEnumerable() bằng .ToList(), nó chỉ được truy vấn một lần.

Tôi có thiếu điều gì đó về những gì AsEnumerable là/không?

+0

Một câu hỏi rất hữu ích để hiểu được hành vi của 'AsEnumerable()' – Lijo

Trả lời

51

Gọi AsEnumerable() không thực hiện truy vấn, liệt kê nó.

IQueryable là giao diện cho phép LINQ to SQL thực hiện phép thuật của nó. IQueryable thực hiện IEnumerable vì vậy khi bạn gọi AsEnumerable(), bạn đang thay đổi phương thức mở rộng được gọi từ đó, tức là từ IQueryable -methods sang IEnumerable -methods (tức là thay đổi từ LINQ to SQL thành LINQ to Objects trong trường hợp cụ thể này). Nhưng bạn đang không thực hiện truy vấn thực tế, chỉ cần thay đổi cách nó sẽ được thực hiện toàn bộ.

Để buộc thực thi truy vấn, bạn phải gọi ToList().

+40

Chắc chắn không chính xác khi nghĩ rằng "không có gì thực sự xảy ra". Trong khi 'AsEnumerable' không đánh giá truy vấn tại thời điểm nó được gọi, nó * chắc chắn * có hiệu lực. Bất kỳ điều gì được gọi thêm trên truy vấn sẽ được đánh giá bằng cách sử dụng LINQ cho các đối tượng, vì vậy bạn không thể soạn các phần tử bổ sung vào truy vấn (một 'Where' hoặc' OrderBy' hoặc bất kỳ thứ gì của bản chất đó) sẽ trở thành một phần của câu lệnh SQL. –

+1

Một ví dụ thực sự tuyệt vời về điều này và tại sao bạn muốn sử dụng nó: Giả sử bạn đang xây dựng một truy vấn và khối đầu tiên kết thúc bằng 'OrderBy (...)', loại này bây giờ là 'IOrderedEnumerable' để sau này con đường bạn có thể tiếp tục chắp thêm 'ThenBy (...)' và thậm chí sau đó bạn có thể nói 'return originalQuery.AsEnumerable()' để đưa nó trở lại một 'IEnumerable' thường xuyên –

+1

Tôi thích ToArray hơn, trừ khi bạn đặc biệt cần thực hiện Danh sách . –

10

Có. Tất cả những gì AsEnumerable sẽ làm là làm cho các hàm Count, SumAverage được thực thi phía máy khách (nói cách khác, nó sẽ mang lại toàn bộ kết quả được đặt cho khách hàng, sau đó khách hàng sẽ thực hiện những tập hợp đó thay vì tạo COUNT()SUM()AVG() câu lệnh trong SQL).

+0

Nhưng quan điểm của OP là anh ta giả định những gì bạn nói là đúng, nhưng thử nghiệm thực nghiệm cho thấy rằng nó không phải là. –

+0

-1 Điều này đơn giản là không đúng sự thật. IQueryable thực hiện IEnumerable để cuộc gọi đến AsEnumerable là một no-op và không thực thi truy vấn. –

+8

@ James, Justin: Bạn hiểu lầm. Tôi không bao giờ nói rằng 'AsEnumerable() 'sẽ khiến truy vấn đánh giá, tôi nói rằng điều duy nhất bổ sung nó sẽ là * khi * các aggregate được đánh giá, chúng sẽ được thực hiện phía máy khách (toàn bộ tập kết quả sẽ được liệt kê * trên máy khách *, và tổng hợp sẽ được tính toán) thay vì được dịch sang câu lệnh SQL. –

0

Tôi cho rằng ToList buộc Linq phải tìm nạp bản ghi từ cơ sở dữ liệu. Khi bạn thực hiện các phép tính tiến hành, chúng được thực hiện dựa vào các đối tượng bộ nhớ thay vì liên quan đến cơ sở dữ liệu.

Rời khỏi loại trả về dưới dạng Số có nghĩa là dữ liệu không được tìm nạp cho đến khi mã được thực hiện bằng cách thực hiện các phép tính. Tôi đoán việc gõ cửa của điều này là cơ sở dữ liệu được nhấn ba lần - một cho mỗi phép tính và dữ liệu không được lưu vào bộ nhớ.

2

Vâng, bạn đang đi đúng hướng. Vấn đề là IQueryable (tuyên bố trước cuộc gọi AsEnumerable) cũng là IEnumerable, vì vậy cuộc gọi đó thực sự là một bước nhảy. Nó sẽ yêu cầu buộc nó vào một cấu trúc dữ liệu trong bộ nhớ cụ thể (ví dụ: ToList()) để buộc truy vấn.

3

Justin Niessner's answer là hoàn hảo.

Tôi chỉ muốn trích dẫn một lời giải thích MSDN ở đây: .NET Language-Integrated Query for Relational Data

Các AsEnumerable() điều hành, không giống như ToList() và ToArray(), không gây thực hiện các truy vấn. Nó vẫn còn trì hoãn.Toán tử AsEnumerable() chỉ thay đổi cách gõ tĩnh của truy vấn, biến IQueryable thành một IEnumerable, lừa trình biên dịch để xử lý phần còn lại của truy vấn như được thực thi cục bộ.

Tôi hy vọng đây là những gì có nghĩa là:

IQueryable-phương pháp để các IEnumerable-phương pháp (tức là thay đổi từ LINQ to SQL để LINQ to Objects

Một khi nó là LINQ đối tượng Chúng ta có thể áp dụng các phương thức của đối tượng (ví dụ: ToString()) Đây là giải thích cho một trong những câu hỏi thường gặp về LINQ - Why LINQ to Entities does not recognize the method 'System.String ToString()?

Acco rding để ASENUMERABLE - codeblog.jonskeet, AsEnumerable có thể có ích khi:

một số khía cạnh của truy vấn trong cơ sở dữ liệu, và thao tác sau đó thêm một chút trong .NET - đặc biệt là nếu có những khía cạnh về cơ bản bạn không thể thực hiện trong LINQ to SQL (hoặc bất kỳ nhà cung cấp nào bạn đang sử dụng).

Nó cũng nói:

Tất cả chúng ta đang làm là thay đổi loại thời gian biên dịch của chuỗi được truyền qua truy vấn của chúng tôi từ IQueryable để IEnumerable - nhưng điều đó có nghĩa là trình biên dịch sẽ sử dụng các phương thức trong Liệt kê (lấy các đại biểu, và thực hiện trong LINQ to Objects) thay vì các phương thức trong Queryable (lấy các cây biểu thức và thường thực thi ngoài quá trình).

Cuối cùng, cũng xem câu hỏi có liên quan này: Returning IEnumerable vs. IQueryable