มีหลายสิ่งที่จะพูดเกี่ยวกับเรื่องนี้ ผมขอมุ่งเน้นAsEnumerableและAsQueryableและกล่าวถึงToList()ไปพร้อมกัน
วิธีการเหล่านี้ทำอะไร
AsEnumerableและAsQueryableโยนหรือแปลงเป็นIEnumerableหรือIQueryableตามลำดับ ฉันว่านักแสดงหรือแปลงด้วยเหตุผล:
เมื่อวัตถุต้นฉบับใช้อินเทอร์เฟซเป้าหมายแล้ววัตถุต้นฉบับจะถูกส่งคืน แต่ส่งไปยังอินเตอร์เฟสเป้าหมาย กล่าวอีกนัยหนึ่ง: ชนิดจะไม่เปลี่ยนแปลง แต่เป็นชนิดเวลารวบรวม
เมื่อวัตถุต้นฉบับไม่ได้ใช้อินเทอร์เฟซเป้าหมายวัตถุต้นฉบับจะถูกแปลงเป็นวัตถุที่ใช้อินเทอร์เฟซเป้าหมาย ดังนั้นทั้งประเภทและเวลารวบรวมจะมีการเปลี่ยนแปลง
ให้ฉันแสดงสิ่งนี้ด้วยตัวอย่าง ฉันมีวิธีเล็ก ๆ นี้ที่รายงานประเภทเวลารวบรวมและประเภทวัตถุจริง ( Jon Skeet ด้วยความอนุเคราะห์ ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
ลองใช้ linq-to-sql โดยพลการTable<T>ซึ่งนำไปใช้IQueryable:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
ผลลัพธ์:
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
คุณจะเห็นว่าคลาสตารางนั้นถูกส่งคืนเสมอ แต่การแสดงนั้นจะเปลี่ยนไป
ตอนนี้วัตถุที่ใช้งานIEnumerableไม่ได้IQueryable:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
ผลลัพธ์ที่ได้:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
นั่นไง AsQueryable()ได้แปลงอาร์เรย์เป็นEnumerableQuery"ซึ่งหมายถึงการIEnumerable<T>รวบรวมเป็นIQueryable<T>แหล่งข้อมูล" (MSDN)
การใช้งานคืออะไร?
AsEnumerableมักใช้เพื่อเปลี่ยนจากIQueryableการใช้งานใด ๆไปเป็น LINQ เป็นวัตถุ (L2O) ส่วนใหญ่เป็นเพราะในอดีตไม่รองรับฟังก์ชั่นที่ L2O มี สำหรับรายละเอียดเพิ่มเติมดูผลของ AsEnumerable () บนเอนทิตี LINQ คืออะไร .
ตัวอย่างเช่นในการสืบค้น Entity Framework เราสามารถใช้วิธีการที่ จำกัด จำนวนเท่านั้น ตัวอย่างเช่นถ้าเราจำเป็นต้องใช้หนึ่งในวิธีการของเราเองในแบบสอบถามเรามักจะเขียนอะไรบางอย่างเช่น
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList - ซึ่งแปลง an IEnumerable<T>เป็น a List<T>- มักใช้เพื่อจุดประสงค์นี้เช่นกัน ข้อดีของการใช้AsEnumerableกับToListคือAsEnumerableไม่ได้เรียกใช้แบบสอบถาม AsEnumerableรักษาการประมวลผลที่เลื่อนออกไปและไม่สร้างรายการกลางที่ไร้ประโยชน์บ่อยครั้ง
ในทางกลับกันเมื่อต้องการบังคับใช้การดำเนินการของแบบสอบถาม LINQ ToListอาจเป็นวิธีการทำเช่นนั้น
AsQueryableสามารถใช้เพื่อทำให้คอลเลกชันที่นับไม่ได้ยอมรับการแสดงออกในงบ LINQ ดูที่นี่สำหรับรายละเอียดเพิ่มเติม: ฉันจำเป็นต้องใช้ AsQueryable () กับชุดสะสมหรือไม่? .
หมายเหตุเกี่ยวกับการใช้สารเสพติด!
AsEnumerableทำงานเหมือนยาเสพติด เป็นการแก้ไขด่วน แต่มีค่าใช้จ่ายและไม่ได้แก้ไขปัญหาพื้นฐาน
ในคำตอบของ Stack Overflow ฉันเห็นคนที่สมัครAsEnumerableเพื่อแก้ไขปัญหาใด ๆ กับวิธีที่ไม่สนับสนุนในนิพจน์ LINQ แต่ราคาไม่ชัดเจนเสมอไป ตัวอย่างเช่นหากคุณทำสิ่งนี้:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... ทุกอย่างถูกแปลอย่างประณีตเป็นคำสั่ง SQL ที่กรอง ( Where) และโครงการ ( Select) นั่นคือทั้งความยาวและความกว้างตามลำดับของชุดผลลัพธ์ SQL จะลดลง
ตอนนี้สมมติว่าผู้ใช้ต้องการดูเฉพาะส่วนของCreateDateวันที่ ใน Entity Framework คุณจะค้นพบว่า ...
.Select(x => new { x.Name, x.CreateDate.Date })
... ไม่รองรับ (ในขณะที่เขียน) อาโชคดีที่มีการAsEnumerableแก้ไข:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
แน่นอนว่ามันอาจจะทำงาน แต่จะดึงทั้งตารางลงในหน่วยความจำจากนั้นจึงใช้ตัวกรองและเส้นโครง คนส่วนใหญ่ฉลาดพอที่จะทำสิ่งWhereแรก:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
แต่คอลัมน์ทั้งหมดจะถูกดึงออกมาก่อนและการฉายภาพจะทำในหน่วยความจำ
การแก้ไขที่แท้จริงคือ:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(แต่นั่นต้องมีความรู้เพิ่มอีกนิด ... )
วิธีการเหล่านี้ไม่ได้ทำอะไร?
กู้คืนความสามารถของ IQueryable
ตอนนี้ข้อแม้สำคัญ เมื่อคุณทำ
context.Observations.AsEnumerable()
.AsQueryable()
IQueryableคุณจะจบลงด้วยวัตถุต้นฉบับแสดงเป็น (เนื่องจากทั้งสองวิธีใช้การส่งและไม่แปลงเท่านั้น)
แต่เมื่อคุณทำ
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
ผลลัพธ์จะเป็นอย่างไร
ผลิตSelect WhereSelectEnumerableIteratorนี่คือระดับสุทธิภายในที่ดำเนินการIEnumerable, ไม่ IQueryableดังนั้นการแปลงเป็นประเภทอื่นจึงเกิดขึ้นและสิ่งที่ตามมาAsQueryableจะไม่สามารถส่งคืนต้นฉบับได้อีกต่อไป
ความหมายนี้ก็คือว่าการใช้AsQueryableคือไม่ได้เป็นวิธีการอย่างน่าอัศจรรย์เป็นผู้ให้บริการฉีดแบบสอบถามที่มีให้บริการเฉพาะของตนลงไปนับ สมมติว่าคุณทำ
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
เงื่อนไขที่จะไม่ถูกแปลเป็น SQL AsEnumerable()ตามด้วยคำสั่ง LINQ ตัดการเชื่อมต่อกับผู้ให้บริการแบบสอบถามเอนทิตีกรอบอย่างแน่นอน
ฉันจงใจแสดงให้เห็นเช่นนี้เพราะผมเคยเห็นที่นี่คำถามที่ผู้คนเช่นลองไปฉีด ' ความสามารถในการเข้าไปในคอลเลกชันโทรInclude AsQueryableมันรวบรวมและเรียกใช้ แต่ไม่ทำอะไรเลยเพราะวัตถุต้นแบบไม่มีIncludeการนำไปใช้อีกต่อไป
ปฏิบัติ
ทั้งAsQueryableและAsEnumerableไม่ดำเนินการ (หรือระบุ ) วัตถุต้นฉบับ พวกเขาเปลี่ยนประเภทหรือการเป็นตัวแทนของพวกเขาเท่านั้น ทั้งอินเทอร์เฟซที่เกี่ยวข้องIQueryableและIEnumerableไม่มีอะไรนอกจาก "การแจงนับที่รอให้เกิดขึ้น" ToList()พวกเขาจะไม่ดำเนินการก่อนที่พวกเขากำลังถูกบังคับให้ทำเช่นนั้นเช่นดังกล่าวข้างต้นโดยการโทร
นั่นหมายความว่ารันIEnumerableได้โดยการเรียกAsEnumerableบนวัตถุจะดำเนินการภายใต้IQueryable IQueryableการดำเนินการตามมาของอีกครั้งจะดำเนินการIEnumerable IQueryableซึ่งอาจมีราคาแพงมาก
การใช้งานเฉพาะ
จนถึงตอนนี้เป็นเพียงเกี่ยวกับวิธีการQueryable.AsQueryableและการEnumerable.AsEnumerableขยาย แต่แน่นอนว่าทุกคนสามารถเขียนวิธีการอินสแตนซ์หรือวิธีการขยายที่มีชื่อเดียวกัน (และฟังก์ชั่น)
ในความเป็นจริงเป็นตัวอย่างทั่วไปของเฉพาะวิธีขยายเป็นAsEnumerable ไม่ได้ใช้งานหรือดังนั้นจึงไม่ใช้วิธีการขยายปกติDataTableExtensions.AsEnumerableDataTableIQueryableIEnumerable