มีหลายสิ่งที่จะพูดเกี่ยวกับเรื่องนี้ ผมขอมุ่งเน้น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.AsEnumerable
DataTable
IQueryable
IEnumerable