Có rất nhiều điều để nói về điều này. Hãy để tôi tập trung AsEnumerablevà AsQueryablevà đề cập ToList()trên đường đi.
Những phương pháp này làm gì?
AsEnumerablevà AsQueryableđúc hoặc chuyển đổi thành IEnumerablehoặc IQueryable, tương ứng. Tôi nói diễn viên hoặc chuyển đổi với một lý do:
Khi đối tượng nguồn đã thực hiện giao diện đích, chính đối tượng nguồn được trả về nhưng được truyền tới giao diện đích. Nói cách khác: loại không thay đổi, nhưng loại thời gian biên dịch là.
Khi đối tượng nguồn không thực hiện giao diện đích, đối tượng nguồn được chuyển đổi thành đối tượng thực hiện giao diện đích. Vì vậy, cả loại và thời gian biên dịch được thay đổi.
Hãy để tôi chỉ ra điều này với một số ví dụ. Tôi đã có phương pháp nhỏ này báo cáo loại thời gian biên dịch và loại thực tế của một đối tượng ( lịch sự Jon Skeet ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Chúng ta hãy thử một linq-to-sql tùy ý Table<T>, thực hiện IQueryable:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Kết quả:
Compile-time type: Table`1
Actual type: Table`1
Compile-time type: IEnumerable`1
Actual type: Table`1
Compile-time type: IQueryable`1
Actual type: Table`1
Bạn thấy rằng chính lớp bảng luôn được trả về, nhưng biểu diễn của nó thay đổi.
Bây giờ một đối tượng thực hiện IEnumerable, không phải IQueryable:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Kết quả:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Nó đây rồi AsQueryable()đã chuyển đổi mảng thành một EnumerableQuery, trong đó "đại diện cho một IEnumerable<T>bộ sưu tập dưới dạng IQueryable<T>nguồn dữ liệu." (MSDN).
Công dụng là gì?
AsEnumerablethường được sử dụng để chuyển từ bất kỳ IQueryabletriển khai nào sang LINQ sang các đối tượng (L2O), chủ yếu là do trước đây không hỗ trợ các chức năng mà L2O có. Để biết thêm chi tiết, xem Hiệu ứng của AsEnumerable () trên Thực thể LINQ là gì? .
Ví dụ: trong truy vấn Entity Framework, chúng ta chỉ có thể sử dụng một số phương thức bị hạn chế. Vì vậy, nếu, ví dụ, chúng ta cần sử dụng một trong các phương thức của riêng mình trong một truy vấn, chúng ta thường sẽ viết một cái gì đó như
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList - mà chuyển đổi một IEnumerable<T>thành List<T>- thường được sử dụng cho mục đích này. Ưu điểm của việc sử dụng AsEnumerablevs. ToListlà AsEnumerablekhông thực hiện truy vấn. AsEnumerablebảo tồn thực thi hoãn lại và không xây dựng một danh sách trung gian thường vô dụng.
Mặt khác, khi thực hiện bắt buộc một truy vấn LINQ là mong muốn, ToListcó thể là một cách để làm điều đó.
AsQueryablecó thể được sử dụng để tạo một bộ sưu tập có thể chấp nhận các biểu thức trong các câu lệnh LINQ. Xem ở đây để biết thêm chi tiết: Tôi có thực sự cần sử dụng AsQueryable () trên bộ sưu tập không? .
Lưu ý về lạm dụng chất!
AsEnumerablehoạt động như một loại thuốc Đó là một sửa chữa nhanh chóng, nhưng với chi phí và nó không giải quyết được vấn đề tiềm ẩn.
Trong nhiều câu trả lời Stack Overflow, tôi thấy mọi người áp dụng AsEnumerableđể khắc phục mọi vấn đề với các phương thức không được hỗ trợ trong các biểu thức LINQ. Nhưng giá không phải lúc nào cũng rõ ràng. Chẳng hạn, nếu bạn làm điều này:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... mọi thứ được dịch gọn gàng thành một câu lệnh SQL lọc ( Where) và dự án ( Select). Nghĩa là, cả chiều dài và chiều rộng tương ứng của tập kết quả SQL đều bị giảm.
Bây giờ giả sử người dùng chỉ muốn xem phần ngày của CreateDate. Trong Entity Framework, bạn sẽ nhanh chóng khám phá ra rằng ...
.Select(x => new { x.Name, x.CreateDate.Date })
... không được hỗ trợ (tại thời điểm viết). Ah, may mắn thay, có AsEnumerablesửa chữa:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Chắc chắn, nó chạy, có lẽ. Nhưng nó kéo toàn bộ bảng vào bộ nhớ và sau đó áp dụng bộ lọc và các phép chiếu. Chà, hầu hết mọi người đều đủ thông minh để làm việc Wheređầu tiên:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Nhưng tất cả các cột vẫn được tìm nạp trước và phép chiếu được thực hiện trong bộ nhớ.
Các sửa chữa thực sự là:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Nhưng điều đó đòi hỏi thêm một chút kiến thức ...)
Những phương pháp này KHÔNG làm gì?
Khôi phục các khả năng của IQueryable
Bây giờ một cảnh báo quan trọng. Khi bạn làm
context.Observations.AsEnumerable()
.AsQueryable()
bạn sẽ kết thúc với đối tượng nguồn được biểu diễn dưới dạng IQueryable. (Bởi vì cả hai phương pháp chỉ truyền và không chuyển đổi).
Nhưng khi bạn làm
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
kết quả sẽ ra sao?
Việc Selectsản xuất a WhereSelectEnumerableIterator. Đây là một lớp .Net nội bộ thực hiện IEnumerable, không phảiIQueryable . Vì vậy, một chuyển đổi sang loại khác đã diễn ra và sau đó AsQueryablekhông bao giờ có thể trả lại nguồn ban đầu nữa.
Hàm ý của việc này là sử dụng AsQueryablelà không phải là một cách để kỳ diệu bơm một nhà cung cấp truy vấn với cụ thể của nó tính năng vào một đếm được. Giả sử bạn làm
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
Điều kiện nơi sẽ không bao giờ được dịch sang SQL. AsEnumerable()theo sau là các câu lệnh LINQ dứt khoát cắt kết nối với nhà cung cấp truy vấn khung thực thể.
Tôi cố tình đưa ra ví dụ này bởi vì tôi đã thấy các câu hỏi ở đây, ví dụ như mọi người cố gắng 'đưa' các Includekhả năng vào một bộ sưu tập bằng cách gọi AsQueryable. Nó biên dịch và chạy, nhưng nó không làm gì cả vì đối tượng cơ bản không còn Includethực hiện nữa.
Hành hình
Cả hai AsQueryablevà AsEnumerablekhông thực thi (hoặc liệt kê ) đối tượng nguồn. Họ chỉ thay đổi loại hoặc đại diện của họ. Cả hai giao diện liên quan, IQueryablevà IEnumerable, không có gì ngoài "một bảng liệt kê đang chờ xảy ra". Họ không được thực hiện trước khi họ bị buộc phải làm như vậy, ví dụ, như đã đề cập ở trên, bằng cách gọi ToList().
Điều đó có nghĩa là thực thi một IEnumerablethu được bằng cách gọi AsEnumerabletrên một IQueryableđối tượng, sẽ thực hiện bên dưới IQueryable. Một thực hiện tiếp theo của di IEnumerablechúc một lần nữa thực hiện IQueryable. Mà có thể rất tốn kém.
Triển khai cụ thể
Cho đến nay, đây chỉ là về các phương pháp Queryable.AsQueryablevà Enumerable.AsEnumerablemở rộng. Nhưng tất nhiên ai cũng có thể viết phương thức cá thể hoặc phương thức mở rộng có cùng tên (và hàm).
Trong thực tế, một ví dụ phổ biến của một AsEnumerablephương pháp mở rộng cụ thể là DataTableExtensions.AsEnumerable. DataTablekhông triển khai IQueryablehoặc IEnumerable, vì vậy các phương thức mở rộng thông thường không áp dụng.