ความแตกต่างระหว่าง. ToList (), .AsEnumerable (), AsQueryable () คืออะไร


182

ฉันรู้ถึงความแตกต่างบางอย่างของ LINQ กับเอนทิตีและ LINQ กับออบเจกต์ซึ่งการใช้งานครั้งแรกIQueryableและการใช้งานครั้งที่สองIEnumerableและขอบเขตคำถามของฉันอยู่ภายใน EF 5

คำถามของฉันคือความแตกต่างทางเทคนิคของ 3 วิธีดังกล่าวอย่างไร ฉันเห็นว่าในหลาย ๆ สถานการณ์พวกเขาทั้งหมดทำงาน ฉันเห็นการใช้ชุดค่าผสมของพวกเขาเช่น.ToList().AsQueryable()กัน

  1. วิธีการเหล่านั้นหมายถึงอะไรกันแน่?

  2. มีปัญหาเรื่องประสิทธิภาพหรืออะไรที่จะนำไปสู่การใช้อันใดอันหนึ่ง?

  3. ทำไมคนเราถึงใช้ตัวอย่างเช่น.ToList().AsQueryable()แทนที่จะเป็น.AsQueryable()?


คำตอบ:


354

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


ขอบคุณสำหรับคำตอบคุณช่วยกรุณาแบ่งปันคำตอบของคุณกับคำถามที่ 3 ของ OP - 3 ได้เพราะเหตุใดจึงใช้เช่น. ToList (). AsQueryable () แทน. AsQueryable ()? ?
kgzdev

@ikram ฉันไม่สามารถคิดอะไรที่จะเป็นประโยชน์ ตามที่ฉันอธิบายการใช้งานAsQueryable()มักขึ้นอยู่กับความเข้าใจผิด แต่ฉันจะปล่อยให้เคี่ยวอยู่ด้านหลังศีรษะของฉันสักพักหนึ่งแล้วดูว่าฉันสามารถเพิ่มความครอบคลุมให้กับคำถามนั้นได้ไหม
Gert Arnold

1
คำตอบที่ดี คุณช่วยกรุณาแม่นยำว่าจะเกิดอะไรขึ้นถ้า IEnumerable ที่ได้รับจากการโทร AsEnumerable () บน IQueryable นั้นมีการแจกแจงหลาย ๆ ครั้ง? แบบสอบถามจะถูกดำเนินการหลายครั้งหรือจะโหลดข้อมูลจากฐานข้อมูลไปยังหน่วยความจำแล้วหรือไม่
antoninod

@antoninod ความคิดที่ดี เสร็จสิ้น
Gert Arnold

46

ToList ()

  • ดำเนินการค้นหาทันที

AsEnumerable ()

  • ขี้เกียจ (ดำเนินการค้นหาในภายหลัง)
  • พารามิเตอร์: Func<TSource, bool>
  • โหลดทุกระเบียนลงในหน่วยความจำแอปพลิเคชันแล้วจัดการ / กรอง (เช่นที่ไหน / Take / Skip จะเลือก * จาก table1 ลงในหน่วยความจำจากนั้นเลือกองค์ประกอบ X แรก) (ในกรณีนี้สิ่งที่มันทำ: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • ขี้เกียจ (ดำเนินการค้นหาในภายหลัง)
  • พารามิเตอร์: Expression<Func<TSource, bool>>
  • แปลง Expression เป็น T-SQL (ด้วยผู้ให้บริการเฉพาะ), ค้นหาจากระยะไกลและโหลดผลลัพธ์ไปยังหน่วยความจำแอปพลิเคชันของคุณ
  • นั่นเป็นเหตุผลที่ DbSet (ใน Entity Framework) ยังสืบทอด IQueryable เพื่อให้ได้แบบสอบถามที่มีประสิทธิภาพ
  • อย่าโหลดทุกระเบียนเช่นถ้าใช้ (5) มันจะสร้างเลือก 5 * SQL ในพื้นหลัง ซึ่งหมายความว่าประเภทนี้เป็นมิตรกับฐานข้อมูล SQL มากกว่าและนี่คือสาเหตุที่ประเภทนี้มักจะมีประสิทธิภาพสูงกว่าและแนะนำให้ใช้เมื่อจัดการกับฐานข้อมูล
  • ดังนั้นAsQueryable()มักจะทำงานได้เร็วกว่าAsEnumerable()ที่สร้าง T-SQL ในตอนแรกซึ่งรวมถึงเงื่อนไขทั้งหมดใน Linq ของคุณ

14

ToList () จะเป็นทุกอย่างในหน่วยความจำแล้วคุณจะทำงานต่อ ดังนั้น ToList (). โดยที่ (ใช้ตัวกรองบางตัว) จะถูกดำเนินการในเครื่อง AsQueryable () จะดำเนินการทุกอย่างจากระยะไกลเช่นตัวกรองที่ถูกส่งไปยังฐานข้อมูลเพื่อนำไปใช้ Queryable ไม่ได้ทำอะไรจนกว่าคุณจะรันมัน ToList อย่างไรก็ตามจะดำเนินการทันที

ดูคำตอบนี้ทำไมต้องใช้ AsQueryable () แทน List () .

แก้ไข: นอกจากนี้ในกรณีของคุณเมื่อคุณทำ ToList () จากนั้นทุกการดำเนินการที่ตามมาคือท้องถิ่นรวมถึง AsQueryable () คุณไม่สามารถเปลี่ยนเป็นรีโมตได้เมื่อคุณเริ่มดำเนินการในเครื่อง หวังว่านี่จะทำให้ชัดเจนกว่านี้อีกเล็กน้อย


2
"AsQueryable () จะดำเนินการทุกอย่างจากระยะไกล" เฉพาะในกรณีที่นับได้แล้วที่สามารถสืบค้นได้ ไม่อย่างนั้นเป็นไปไม่ได้และทุกอย่างยังคงทำงานอยู่ในเครื่อง คำถามมี ".... ToList (). AsQueryable ()" ซึ่งสามารถใช้การชี้แจงบางอย่างในคำตอบของคุณ IMO

2

พบประสิทธิภาพที่ไม่ดีในรหัสด้านล่าง

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

แก้ไขด้วย

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

สำหรับ IQueryable ให้อยู่ใน IQueryable เมื่อเป็นไปได้พยายามอย่าใช้เช่น IEnumerable

ปรับปรุง มันสามารถทำให้ง่ายขึ้นในการแสดงออกขอบคุณเกิร์ตอาร์โนล

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.