Có rất nhiều điều để nói về điều này. Hãy để tôi tập trung AsEnumerable
và AsQueryable
và đề cập ToList()
trên đường đi.
Những phương pháp này làm gì?
AsEnumerable
và AsQueryable
đúc hoặc chuyển đổi thành IEnumerable
hoặ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ì?
AsEnumerable
thường được sử dụng để chuyển từ bất kỳ IQueryable
triể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 AsEnumerable
vs. ToList
là AsEnumerable
không thực hiện truy vấn. AsEnumerable
bả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, ToList
có thể là một cách để làm điều đó.
AsQueryable
có 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!
AsEnumerable
hoạ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ó AsEnumerable
sử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 Select
sả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 đó AsQueryable
khô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 AsQueryable
là 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 Include
khả 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 Include
thực hiện nữa.
Hành hình
Cả hai AsQueryable
và AsEnumerable
khô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, IQueryable
và 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 IEnumerable
thu được bằng cách gọi AsEnumerable
trê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 IEnumerable
chú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.AsQueryable
và Enumerable.AsEnumerable
mở 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 AsEnumerable
phương pháp mở rộng cụ thể là DataTableExtensions.AsEnumerable
. DataTable
không triển khai IQueryable
hoặc IEnumerable
, vì vậy các phương thức mở rộng thông thường không áp dụng.