การส่งคืน IENumerable <T> เทียบกับ IQueryable <T>


1084

อะไรคือความแตกต่างระหว่างการกลับมาIQueryable<T>และการกลับมาIEnumerable<T>อีกครั้งหนึ่งจะได้รับความนิยมมากกว่าเมื่อใด

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

คำตอบ:


1778

ใช่ทั้งสองอย่างจะให้การดำเนินการที่ถูกเลื่อนออกไปกับคุณ

ความแตกต่างคือนั่นIQueryable<T>คืออินเทอร์เฟซที่อนุญาตให้ LINQ-to-SQL (LINQ. -to-Anything จริงๆ) ทำงานได้ ดังนั้นหากคุณปรับแต่งข้อความค้นหาของคุณเพิ่มเติมIQueryable<T>แบบสอบถามจะถูกดำเนินการในฐานข้อมูลหากเป็นไปได้

สำหรับIEnumerable<T>กรณีนี้จะเป็น LINQ-to-object ซึ่งหมายความว่าวัตถุทั้งหมดที่ตรงกับแบบสอบถามต้นฉบับจะต้องโหลดลงในหน่วยความจำจากฐานข้อมูล

ในรหัส:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

รหัสนั้นจะดำเนินการ SQL เพื่อเลือกลูกค้าระดับทองเท่านั้น ในทางกลับกันรหัสต่อไปนี้จะดำเนินการแบบสอบถามต้นฉบับในฐานข้อมูลจากนั้นกรองลูกค้าที่ไม่ใช่ทองในหน่วยความจำ:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

นี่เป็นข้อแตกต่างที่สำคัญและการทำงานIQueryable<T>ในหลายกรณีสามารถช่วยคุณไม่ให้ส่งคืนแถวมากเกินไปจากฐานข้อมูล อีกตัวอย่างที่สำคัญคือการเพจ: ถ้าคุณใช้TakeและSkipเปิดIQueryableคุณจะได้รับจำนวนแถวที่ร้องขอเท่านั้น การทำเช่นนั้นบนIEnumerable<T>จะทำให้แถวทั้งหมดของคุณถูกโหลดในหน่วยความจำ


32
คำอธิบายที่ดี มีสถานการณ์ใดบ้างที่ IEnumerable จะดีกว่าในการ IQueryable?
fjxx

8
ดังนั้นเราสามารถพูดได้ว่าถ้าเราใช้ IQueryable เพื่อสืบค้น Memory Object แล้วพวกเขาจะไม่แตกต่างกันระหว่าง IEnumerable และ IQueryable?
Tarik

11
คำเตือน: แม้ว่า IQueryable อาจเป็นโซลูชันที่ดึงดูดเนื่องจากการเพิ่มประสิทธิภาพที่ระบุไว้ แต่ก็ไม่ควรได้รับอนุญาตผ่านที่เก็บหรือเลเยอร์บริการ นี่คือการปกป้องฐานข้อมูลของคุณจากค่าใช้จ่ายที่เกิดจาก "นิพจน์ LINQ ซ้อน"
Yorro

48
@fjxx ใช่ หากคุณต้องการการกรองซ้ำกับผลลัพธ์ดั้งเดิมของคุณ (ผลลัพธ์สุดท้ายหลายรายการ) การทำเช่นนั้นบนส่วนต่อประสาน IQueryable จะทำให้การปัดเศษหลายครั้งไปยังฐานข้อมูลซึ่งการทำเช่นนั้นบน IEnumerable จะทำการกรองในหน่วยความจำทำให้เร็วขึ้น (เว้นแต่จำนวนข้อมูลมีขนาดใหญ่)
ต่อHornshøj-Schierbeck

34
เหตุผลที่จะต้องการอีกIEnumerableที่จะIQueryableเป็นที่ไม่ดำเนินงานทั้งหมด LINQ รับการสนับสนุนจากผู้ให้บริการ LINQ ดังนั้นตราบใดที่คุณรู้ว่าคุณกำลังทำอะไรคุณสามารถใช้IQueryableเพื่อส่งข้อความค้นหาไปยังผู้ให้บริการ LINQ (LINQ2SQL, EF, NHibernate, MongoDB เป็นต้น) แต่ถ้าคุณปล่อยให้รหัสอื่นทำตามที่คุณต้องการIQueryableในที่สุดคุณก็จะเจอปัญหาเพราะรหัสลูกค้าบางแห่งใช้การทำงานที่ไม่ได้รับการสนับสนุน ฉันเห็นด้วยกับข้อเสนอแนะที่จะไม่ปล่อยIQueryable"เข้าป่า" ที่ผ่านมาพื้นที่เก็บข้อมูลหรือชั้นที่เทียบเท่า
Avish

302

คำตอบที่ดีคือดี แต่มันไม่ได้พูดถึงต้นไม้แสดงออกซึ่งอธิบายว่า "วิธี" ทั้งสองอินเตอร์เฟสแตกต่างกันอย่างไร โดยทั่วไปมีส่วนขยาย LINQ สองชุดที่เหมือนกัน Where(), Sum(), Count(), FirstOrDefault()ฯลฯ ทุกคนมีสองรุ่น: หนึ่งที่ยอมรับฟังก์ชั่นและคนที่ยอมรับการแสดงออก

  • IEnumerableลายเซ็นรุ่น:Where(Func<Customer, bool> predicate)

  • IQueryableลายเซ็นรุ่น:Where(Expression<Func<Customer, bool>> predicate)

คุณอาจใช้ทั้งสองอย่างโดยไม่รู้ตัวเพราะทั้งคู่เรียกว่าใช้ไวยากรณ์ที่เหมือนกัน:

เช่นWhere(x => x.City == "<City>")ทำงานทั้งIEnumerableและIQueryable

  • เมื่อใช้Where()กับIEnumerableคอลเล็กชันคอมไพเลอร์จะส่งผ่านฟังก์ชันที่คอมไพล์แล้วไปWhere()

  • เมื่อใช้Where()ในคอลเลกชันคอมไพเลอร์ผ่านต้นไม้แสดงออกถึงความIQueryable Where()ต้นไม้นิพจน์เป็นเหมือนระบบการสะท้อนภาพ แต่เป็นรหัส คอมไพเลอร์แปลงรหัสของคุณเป็นโครงสร้างข้อมูลที่อธิบายสิ่งที่รหัสของคุณทำในรูปแบบที่ย่อยง่าย

ทำไมต้องกังวลกับสิ่งที่แสดงออกของต้นไม้นี้? ฉันแค่ต้องการWhere()กรองข้อมูลของฉัน เหตุผลหลักคือทั้ง ORM ของ EF และ Linq2SQL สามารถแปลงแผนผังต้นไม้นิพจน์เป็น SQL โดยตรงซึ่งโค้ดของคุณจะทำงานได้เร็วขึ้นมาก

โอ้ฟังดูเหมือนการเพิ่มประสิทธิภาพฟรีฉันควรใช้AsQueryable()ทั่วทุกที่ในกรณีนั้นหรือไม่? ไม่มีIQueryableประโยชน์ก็ต่อเมื่อผู้ให้บริการข้อมูลพื้นฐานสามารถทำอะไรกับมันได้ การแปลงบางอย่างเป็นประจำListเพื่อIQueryableไม่ให้ประโยชน์ใด ๆ แก่คุณ


9
IMO ดีกว่าคำตอบที่ยอมรับ อย่างไรก็ตามฉันไม่ได้รับสิ่งใดสิ่งหนึ่ง: IQueryable ไม่ได้ให้ประโยชน์ใด ๆ กับวัตถุปกติตกลง แต่มันแย่กว่านี้ในทางใดทางหนึ่งหรือไม่? เพราะถ้ามันไม่ให้ผลประโยชน์ใด ๆ มันก็ไม่มีเหตุผลเพียงพอที่จะชอบ IEnumerable ดังนั้นความคิดในการใช้ IQueryable ทั่วสถานที่ก็ยังคงใช้ได้
Sergei Tachenov

1
Sergey, IQueryable ขยาย IEnumerable ดังนั้นเมื่อใช้ IQueryable คุณโหลดได้มากขึ้นในหน่วยความจำมากกว่า IEnumerable instantiation! ดังนั้นนี่คือข้อโต้แย้ง ( stackoverflow.com/questions/12064828/… c ++ ถึงแม้ว่าฉันคิดว่าฉันสามารถคาดการณ์สิ่งนี้ได้)
ไวกิ้ง

เห็นด้วยกับ Sergei เกี่ยวกับเรื่องนี้เป็นคำตอบที่ดีที่สุด (แม้ว่าคำตอบที่ยอมรับได้ดี) ฉันต้องการเพิ่มว่าในประสบการณ์ของผมIQueryableไม่ฟังก์ชั่นไม่ได้แยกเช่นเดียวกับIEnumerableไม่: ยกตัวอย่างเช่นถ้าคุณต้องการที่จะทราบว่าองค์ประกอบของDBSet<BookEntity>ไม่ได้อยู่ในList<BookObject>, พ่นยกเว้น:dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e)) Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'ผมต้องเพิ่มหลัง.ToList() dbSetObject
Jean-David Lanz

80

ใช่ทั้งสองใช้การดำเนินการรอการตัดบัญชี เรามาแสดงให้เห็นถึงความแตกต่างโดยใช้ตัวสร้างโปรไฟล์ SQL Server ....

เมื่อเราเรียกใช้รหัสต่อไปนี้:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

ใน Profiler ของ SQL Server เราพบคำสั่งเท่ากับ:

"SELECT * FROM [dbo].[WebLog]"

ใช้เวลาประมาณ 90 วินาทีในการเรียกใช้บล็อกโค้ดนั้นกับตาราง WebLog ซึ่งมี 1 ล้านเรคคอร์ด

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

เมื่อเราใช้IQueryableแทนIEnumerableตัวอย่างข้างต้น (บรรทัดที่สอง):

ใน Profiler ของ SQL Server เราพบคำสั่งเท่ากับ:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

IQueryableมันประมาณใช้เวลาสี่วินาทีในการเรียกใช้บล็อกของรหัสนี้โดยใช้

IQueryable มีคุณสมบัติที่เรียกว่าExpressionซึ่งเก็บนิพจน์ทรีซึ่งเริ่มสร้างเมื่อเราใช้resultในตัวอย่างของเรา (ซึ่งเรียกว่าการประมวลผลที่เลื่อนออกไป) และในตอนท้ายนิพจน์นี้จะถูกแปลงเป็นแบบสอบถาม SQL เพื่อรันบนเอ็นจินฐานข้อมูล


5
สิ่งนี้สอนให้ฉันทราบเมื่อทำการคัดเลือกให้กับ IEnumerable แล้ว IQueryable ต้นแบบจะเสียวิธีการขยาย IQueryable
Yiping

56

ทั้งสองอย่างจะให้การประหารชีวิตคุณล่าช้า

สำหรับที่เป็นที่ต้องการมากกว่าอื่น ๆ มันขึ้นอยู่กับแหล่งข้อมูลพื้นฐานของคุณคืออะไร

การส่งกลับIEnumerableจะเป็นการบังคับให้รันไทม์ใช้ LINQ ไปยังวัตถุเพื่อสืบค้นคอลเลกชันของคุณโดยอัตโนมัติ

การคืนค่าIQueryable(ซึ่งดำเนินการIEnumerableตามวิธี) มอบฟังก์ชันพิเศษเพื่อแปลแบบสอบถามของคุณเป็นสิ่งที่อาจทำงานได้ดีขึ้นในแหล่งข้อมูลพื้นฐาน (LINQ เป็น SQL, LINQ เป็น XML, ฯลฯ )


30

โดยทั่วไปฉันจะแนะนำสิ่งต่อไปนี้:

  • ส่งคืนIQueryable<T>ถ้าคุณต้องการเปิดใช้งานนักพัฒนาโดยใช้วิธีการของคุณเพื่อปรับแต่งแบบสอบถามที่คุณส่งคืนก่อนดำเนินการ

  • ส่งคืนIEnumerableถ้าคุณต้องการขนส่งชุดของวัตถุเพื่อระบุ

ลองนึกภาพIQueryableว่ามันคืออะไร - "แบบสอบถาม" สำหรับข้อมูล (ซึ่งคุณสามารถปรับแต่งได้ถ้าคุณต้องการ) An IEnumerableคือชุดของวัตถุ (ซึ่งได้รับแล้วหรือถูกสร้างขึ้น) ซึ่งคุณสามารถระบุได้


2
"สามารถระบุได้" ไม่ "สามารถนับได้ IE"
Casey

28

ก่อนหน้านี้มีการพูดกันมากมาย แต่กลับไปที่รากด้วยวิธีการทางเทคนิคที่มากขึ้น:

  1. IEnumerable คือชุดของวัตถุในหน่วยความจำที่คุณสามารถระบุได้ - ลำดับในหน่วยความจำที่ทำให้สามารถวนซ้ำผ่านได้ (ทำให้เป็นเรื่องง่ายสำหรับการforeachวนซ้ำภายในแม้ว่าคุณจะสามารถทำได้ด้วยIEnumeratorเท่านั้น) พวกเขาอยู่ในหน่วยความจำตามที่เป็นอยู่
  2. IQueryable เป็นต้นไม้ที่แสดงออกว่าจะได้รับการแปลเป็นอย่างอื่นในบางจุดที่มีความสามารถในการระบุมากกว่าผลสุดท้าย ฉันเดาว่านี่คือสิ่งที่คนส่วนใหญ่สับสน

เห็นได้ชัดว่าพวกเขามีความหมายที่แตกต่างกัน

IQueryableแทนทรีนิพจน์ (เคียวรีเพียงอย่างเดียว) ที่จะถูกแปลเป็นอย่างอื่นโดยผู้ให้บริการการสืบค้นพื้นฐานทันทีที่มีการเรียก APIs ออกมาเช่นฟังก์ชั่นรวม LINQ (Sum, Count, etc. ) หรือ ToList [Array, Dictionary, .. ] และIQueryableยังใช้วัตถุIEnumerable, IEnumerable<T>เพื่อที่ว่าถ้าพวกเขาเป็นตัวแทนแบบสอบถามผลจากแบบสอบถามที่อาจจะซ้ำ มันหมายความว่า IQueryable ไม่จำเป็นต้องเป็นข้อความค้นหาเท่านั้น คำที่ถูกต้องคือพวกเขามีต้นไม้แสดงออก

ตอนนี้วิธีการดำเนินการนิพจน์เหล่านั้นและสิ่งที่พวกเขาหันไปเป็นทั้งหมดขึ้นอยู่กับที่เรียกว่าผู้ให้บริการแบบสอบถาม (ตัวจัดการนิพจน์ที่เราสามารถคิดได้)

ในโลกEntity Framework (ซึ่งเป็นผู้ให้บริการแหล่งข้อมูลที่แฝงตัวอยู่หรือIQueryableนิพจน์ผู้ให้บริการแบบสอบถาม) จะถูกแปลเป็นเคียวรีT-SQLดั้งเดิม Nhibernateทำสิ่งที่คล้ายกันกับพวกเขา คุณสามารถเขียนบทความของคุณเองตามแนวคิดที่อธิบายไว้อย่างดีในLINQ: การสร้างลิงก์ผู้ให้บริการ IQueryableและคุณอาจต้องการให้มี API การสืบค้นที่กำหนดเองสำหรับบริการผู้ให้บริการร้านค้าผลิตภัณฑ์ของคุณ

ดังนั้นIQueryableวัตถุจะถูกสร้างขึ้นมานานจนกว่าเราจะปล่อยพวกมันออกมาอย่างชัดเจนและบอกให้ระบบเขียนมันลงใน SQL หรืออะไรก็ตามและส่งลงโซ่ปฏิบัติการเพื่อประมวลผลต่อไป

ราวกับว่าการดำเนินการที่เลื่อนออกไปมันเป็นLINQคุณสมบัติที่จะทำให้โครงร่างนิพจน์ทรีในหน่วยความจำและส่งไปยังการดำเนินการตามความต้องการเท่านั้นเมื่อใดก็ตามที่มีการเรียก API บางตัวเทียบกับลำดับ (Count เดียวกัน ToList เป็นต้น)

การใช้งานที่เหมาะสมของทั้งสองอย่างหนักนั้นขึ้นอยู่กับงานที่คุณเผชิญในกรณีเฉพาะ สำหรับรูปแบบพื้นที่เก็บข้อมูลที่รู้จักกันดีฉันเลือกที่จะกลับมาเป็นการส่วนตัวIListนั่นคือIEnumerableรายการ (ดัชนีและสิ่งที่ชอบ) ดังนั้นคำแนะนำของฉันคือใช้IQueryableเฉพาะในที่เก็บและ IEnumerable ที่ใดก็ได้ในรหัส ไม่พูดเกี่ยวกับข้อกังวลเกี่ยวกับความสามารถในการทดสอบที่IQueryableแยกย่อยและทำลายหลักการแยกข้อกังวล หากคุณส่งคืนนิพจน์จากภายในที่เก็บข้อมูลที่ผู้บริโภคอาจเล่นด้วยเลเยอร์คงอยู่ตามที่พวกเขาต้องการ

นอกเหนือจากระเบียบเล็กน้อย :) (จากการอภิปรายในความคิดเห็น)) ไม่มีวัตถุใด ๆ ในหน่วยความจำเนื่องจากมันไม่ใช่ประเภทจริงต่อคนพวกเขาเป็นเครื่องหมายประเภท - ถ้าคุณต้องการที่จะไปที่ลึก แต่มันก็สมเหตุสมผล (และนั่นเป็นสาเหตุที่แม้แต่MSDNก็ใช้วิธีนี้) ในการคิดว่า IEnumerables เป็นคอลเลกชันในหน่วยความจำในขณะที่ IQueryables เป็นต้นไม้ที่แสดงออก ประเด็นคืออินเทอร์เฟซ IQueryable สืบทอดอินเทอร์เฟซ IEnumerable ดังนั้นถ้ามันแสดงถึงแบบสอบถามผลลัพธ์ของแบบสอบถามนั้นสามารถระบุได้ การแจงนับทำให้แผนภูมินิพจน์ที่เกี่ยวข้องกับวัตถุ IQueryable ถูกเรียกใช้ ดังนั้นในความเป็นจริงคุณไม่สามารถเรียกสมาชิก IEnumerable ใด ๆ ได้โดยไม่ต้องมีวัตถุในหน่วยความจำ มันจะเข้าไปที่นั่นถ้าคุณทำยังไงก็ตามถ้ามันไม่ว่าง IQueryables เป็นเพียงแบบสอบถามไม่ใช่ข้อมูล


3
ความคิดเห็นที่ IEnumerables อยู่ในหน่วยความจำเสมอไม่ใช่ความจริงที่ไม่จำเป็น ส่วนต่อประสาน IQueryable ใช้ส่วนต่อประสาน IEnumerable ด้วยเหตุนี้คุณสามารถผ่าน IQueryable แบบดิบซึ่งแสดงถึงคิวรี LINQ-to-SQL ลงในมุมมองซึ่งคาดว่า IEnumerable! คุณอาจประหลาดใจที่พบว่าบริบทข้อมูลของคุณหมดอายุหรือมีปัญหากับ MARS (ชุดผลลัพธ์ที่ใช้งานอยู่หลายชุด)

ดังนั้นในความเป็นจริงคุณไม่สามารถเรียกสมาชิก IEnumerable ใด ๆ โดยไม่ต้องมีวัตถุในหน่วยความจำ มันจะเข้าไปที่นั่นถ้าคุณทำยังไงก็ตามถ้ามันไม่ว่าง IQueryables เป็นเพียงแบบสอบถามไม่ใช่ข้อมูล แต่ฉันเห็นจุดของคุณจริงๆ ฉันจะเพิ่มความคิดเห็นเกี่ยวกับเรื่องนี้
Arman McHitarian

@AlexanderPritchard ไม่มีสิ่งใดที่เป็นวัตถุในหน่วยความจำเนื่องจากไม่ใช่ประเภทจริงต่อ se เป็นเครื่องหมายประเภท - หากคุณต้องการที่จะลึก แต่มันก็สมเหตุสมผล (และนั่นเป็นสาเหตุที่แม้แต่ MSDN ก็ใช้วิธีนี้) ในการคิดว่า IEnumerables เป็นคอลเลกชันในหน่วยความจำในขณะที่ IQueryables เป็นต้นไม้ที่แสดงออก ประเด็นคืออินเทอร์เฟซ IQueryable สืบทอดอินเทอร์เฟซ IEnumerable ดังนั้นถ้ามันแสดงถึงแบบสอบถามผลลัพธ์ของแบบสอบถามนั้นสามารถระบุได้ การแจงนับทำให้แผนภูมินิพจน์ที่สัมพันธ์กับวัตถุ IQueryable ถูกเรียกใช้
Arman McHitarian

24

โดยทั่วไปคุณต้องการเก็บรักษาแบบคงที่ดั้งเดิมของแบบสอบถามจนกว่าจะมีความสำคัญ

ด้วยเหตุนี้คุณสามารถกำหนดตัวแปรของคุณเป็น 'var' แทนอย่างใดอย่างหนึ่งIQueryable<>หรือIEnumerable<>และคุณจะรู้ว่าคุณไม่ได้เปลี่ยนชนิด

หากคุณเริ่มต้นด้วยการIQueryable<>คุณมักจะต้องการให้มันเป็นIQueryable<>จนกว่าจะมีเหตุผลที่น่าสนใจที่จะเปลี่ยนมัน สาเหตุของสิ่งนี้คือคุณต้องการให้ข้อมูลตัวประมวลผลแบบสอบถามมากที่สุด ตัวอย่างเช่นหากคุณจะใช้เพียง 10 ผลลัพธ์ (คุณเรียกTake(10)แล้ว) คุณต้องการให้ SQL Server รู้เกี่ยวกับสิ่งนั้นเพื่อให้สามารถปรับแผนแบบสอบถามให้เหมาะสมและส่งเฉพาะข้อมูลที่คุณจะใช้

เหตุผลที่น่าสนใจในการเปลี่ยนประเภทจากIQueryable<>เป็นIEnumerable<>อาจเป็นเพราะคุณกำลังเรียกใช้ฟังก์ชันส่วนขยายที่การใช้งานIQueryable<>ในวัตถุเฉพาะของคุณไม่สามารถจัดการหรือจัดการอย่างไม่มีประสิทธิภาพ ในกรณีนั้นคุณอาจต้องการแปลงชนิดเป็นIEnumerable<>(โดยการกำหนดให้กับตัวแปรประเภทIEnumerable<>หรือโดยใช้AsEnumerableวิธีการขยายตัวอย่างเช่น) เพื่อให้ฟังก์ชันส่วนขยายที่คุณเรียกท้ายเป็นคนในEnumerableชั้นเรียนแทนQueryableชั้นเรียน


18

มีการโพสต์บล็อกที่มีตัวอย่างซอร์สโค้ดสั้น ๆ เกี่ยวกับวิธีการใช้ในทางที่ผิดIEnumerable<T>อาจส่งผลกระทบต่อประสิทธิภาพการค้นหาของ LINQ อย่างมาก: Entity Framework: IQueryable vs. IEnumerableIEnumerable

หากเราขุดลึกลงไปและมองเข้าไปในแหล่งที่มาเราจะเห็นว่ามีวิธีการขยายที่แตกต่างกันอย่างเห็นได้ชัดคือ perfomed สำหรับIEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

และIQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

อันแรกคืนค่า iterator ที่นับได้และอันที่สองสร้างเคียวรีผ่านผู้ให้บริการเคียวรีที่ระบุในIQueryableแหล่งที่มา


11

ฉันเพิ่งพบปัญหากับIEnumerablev IQueryable. อัลกอริทึมที่ใช้ในการดำเนินการIQueryableแบบสอบถามเพื่อให้ได้ชุดผลลัพธ์ สิ่งเหล่านี้จะถูกส่งผ่านไปยังforeachลูปด้วยไอเท็มอินสแตนซ์เป็นคลาส Entity Framework (EF) คลาส EF นี้จะถูกใช้ในfromส่วนของคำสั่ง Linq to Entity ซึ่งทำให้เกิดผลลัพธ์IEnumerableก่อให้เกิดผลที่ตามมาจะเป็น

ฉันค่อนข้างใหม่สำหรับ EF และ Linq สำหรับกิจการดังนั้นจึงใช้เวลาสักครู่ในการพิจารณาว่าคอขวดคืออะไร ใช้ MiniProfiling ฉันพบแบบสอบถามแล้วแปลงการดำเนินการแต่ละรายการให้เป็นIQueryableLinq สำหรับ Entities แบบสอบถามเดียว IEnumerableเอา 15 วินาทีและIQueryableใช้เวลา 0.5 วินาทีในการดำเนินการ มีสามตารางที่เกี่ยวข้องและหลังจากอ่านสิ่งนี้แล้วฉันเชื่อว่าIEnumerableแบบสอบถามจริง ๆ แล้วสร้างผลิตภัณฑ์ข้ามตารางสามตัวและกรองผลลัพธ์

ลองใช้ IQueryables เป็นกฎของหัวแม่มือและโปรไฟล์งานของคุณเพื่อทำการเปลี่ยนแปลงที่วัดได้


เหตุผลก็คือนิพจน์ IQueryable ถูกแปลงเป็น SQL ดั้งเดิมใน EF และถูกดำเนินการใน DB ขณะที่รายการ IEnumerable เป็นวัตถุในหน่วยความจำ พวกเขาได้รับข้อมูลจากฐานข้อมูลในบางจุดเมื่อคุณเรียกใช้ฟังก์ชันการรวมเช่น Count, Sum หรือ To ... และทำงานในหน่วยความจำหลังจากนั้น IQueryables ยังติดอยู่ในหน่วยความจำเมื่อคุณเรียกหนึ่งใน API เหล่านั้น แต่ถ้าไม่คุณสามารถส่งผ่านนิพจน์สแต็กของเลเยอร์และเล่นกับตัวกรองจนถึงการเรียก API การออกแบบที่ดี DAL ที่ดีได้รับการออกแบบพื้นที่เก็บข้อมูลจะแก้ปัญหาแบบนี้;)
Arman McHitarian

10

ฉันต้องการชี้แจงบางสิ่งเนื่องจากคำตอบที่ขัดแย้งกันดูเหมือนจะเกิดขึ้น

(1) IQueryableขยายIEnumerableส่วนต่อประสาน (คุณสามารถส่งIQueryableสิ่งที่คาดหวังIEnumerableโดยไม่มีข้อผิดพลาด)

(2) ทั้งคู่IQueryableและIEnumerableLINQ พยายามทำการโหลดอย่างเกียจคร้านเมื่อวนซ้ำชุดผลลัพธ์ (โปรดทราบว่าการใช้งานสามารถเห็นได้ในวิธีการขยายส่วนต่อประสานสำหรับแต่ละประเภท)

ในคำอื่น ๆIEnumerablesไม่ได้เป็นเพียง "ในหน่วยความจำ" IQueryablesไม่ได้ถูกเรียกใช้งานบนฐานข้อมูลเสมอไป IEnumerableจะต้องโหลดสิ่งต่าง ๆ ลงในหน่วยความจำ (เมื่อดึงออกมาอาจเป็นไปอย่างขี้เกียจ) เพราะไม่มีผู้ให้บริการข้อมูลที่เป็นนามธรรม IQueryablesพึ่งพาผู้ให้บริการที่เป็นนามธรรม (เช่น LINQ-to-SQL) แม้ว่าสิ่งนี้อาจเป็นผู้ให้บริการในหน่วยความจำ. NET

ตัวอย่างการใช้งาน

(a) ดึงรายการบันทึกIQueryableจากบริบทของ EF (ไม่มีบันทึกอยู่ในหน่วยความจำ)

(ข) ผ่านไปยังมุมมองที่มีรูปแบบเป็นIQueryable IEnumerable(ถูกต้องIQueryableขยายIEnumerable )

(c) วนซ้ำและเข้าถึงระเบียนของเอนทิตีลูกและคุณสมบัติของชุดข้อมูลจากมุมมอง (อาจทำให้เกิดข้อยกเว้น!)

ปัญหาที่เป็นไปได้

(1) IEnumerableความพยายามโหลดขี้เกียจและบริบทข้อมูลของคุณหมดอายุ มีข้อผิดพลาดเกิดขึ้นเนื่องจากไม่มีผู้ให้บริการอีกต่อไป

(2) พร็อกซีเอนทิตีของ Entity Framework เปิดใช้งาน (ค่าเริ่มต้น) และคุณพยายามเข้าถึงวัตถุที่เกี่ยวข้อง (เสมือน) ที่มีบริบทข้อมูลที่หมดอายุ เช่นเดียวกับ (1)

(3) ชุดผลลัพธ์ที่ใช้งานอยู่หลายชุด (MARS) หากคุณกำลังวนซ้ำIEnumerableในforeach( var record in resultSet )บล็อกและพยายามเข้าถึงพร้อมกันrecord.childEntity.childPropertyคุณอาจลงเอยด้วย MARS เนื่องจากการโหลดชุดข้อมูลและเอนทิตีสัมพันธ์ สิ่งนี้จะทำให้เกิดข้อยกเว้นหากไม่ได้เปิดใช้งานในสตริงการเชื่อมต่อของคุณ

สารละลาย

  • ฉันพบว่าการเปิดใช้งาน MARS ในสตริงการเชื่อมต่อนั้นใช้งานไม่ได้ ฉันขอแนะนำให้คุณหลีกเลี่ยง MARS เว้นแต่จะมีความเข้าใจและต้องการอย่างชัดเจน

ดำเนินการค้นหาและจัดเก็บผลลัพธ์โดยการเรียกใช้resultList = resultSet.ToList() นี่เป็นวิธีที่ตรงไปตรงมาที่สุดในการรับรองเอนทิตีของคุณในหน่วยความจำ

ในกรณีที่คุณกำลังเข้าถึงหน่วยงานที่เกี่ยวข้องคุณอาจต้องใช้บริบทข้อมูล ทั้งที่หรือคุณสามารถปิดผู้รับมอบฉันทะนิติบุคคลและชัดเจนหน่วยงานที่เกี่ยวข้องจากคุณIncludeDbSet


9

ความแตกต่างที่สำคัญระหว่าง "IEnumerable" และ "IQueryable" เป็นเรื่องเกี่ยวกับที่ดำเนินการตรรกะตัวกรอง หนึ่งรันบนฝั่งไคลเอ็นต์ (ในหน่วยความจำ) และอีกหนึ่งรันบนฐานข้อมูล

ตัวอย่างเช่นเราสามารถพิจารณาตัวอย่างที่เรามี 10,000 เร็กคอร์ดสำหรับผู้ใช้ในฐานข้อมูลของเราและสมมติว่ามีเพียง 900 รายการที่เป็นผู้ใช้ที่ใช้งานอยู่ดังนั้นในกรณีนี้ถ้าเราใช้“ IEnumerable” จากนั้นก่อนจะโหลดทั้งหมด 10,000 รายการในหน่วยความจำ จากนั้นใช้ตัวกรอง IsActive ที่จะส่งคืนผู้ใช้ที่ใช้งาน 900 รายในที่สุด

ในขณะที่ในกรณีเดียวกันถ้าเราใช้ "IQueryable" มันจะใช้ตัวกรอง IsActive โดยตรงในฐานข้อมูลซึ่งโดยตรงจากที่นั่นจะส่งคืนผู้ใช้ที่ใช้งาน 900 คน

ลิงค์อ้างอิง


อันไหนที่ถูกปรับให้เหมาะสมและมีน้ำหนักเบาในแง่ของประสิทธิภาพ?
Sitecore Sam

@ Sam "IQueryable" เป็นที่ต้องการมากกว่าในแง่ของการเพิ่มประสิทธิภาพและน้ำหนักเบา
Tabish Usman

6

เราสามารถใช้ทั้งสองอย่างในลักษณะเดียวกันและมีความแตกต่างในประสิทธิภาพเท่านั้น

IQueryable ดำเนินการกับฐานข้อมูลอย่างมีประสิทธิภาพ หมายความว่ามันสร้างแบบสอบถามแบบใช้เลือกข้อมูลทั้งหมดและรับเฉพาะระเบียนที่เกี่ยวข้องเท่านั้น

ตัวอย่างเช่นเราต้องการให้ลูกค้า10 อันดับแรกที่มีชื่อขึ้นต้นด้วย 'Nimal' select top 10 * from Customer where name like ‘Nimal%’ในกรณีนี้แบบสอบถามเลือกจะถูกสร้างเป็น

แต่ถ้าเราใช้ IEnumerable แบบสอบถามจะเป็นเช่นนั้นselect * from Customer where name like ‘Nimal%’และสิบอันดับแรกจะถูกกรองในระดับการเข้ารหัส C # (จะได้รับระเบียนลูกค้าทั้งหมดจากฐานข้อมูลและส่งผ่านไปยัง C #)


5

นอกจากคำตอบที่ดี 2 ข้อแรก (โดย driis & by Jacob):

IEnumerable interface อยู่ใน System.Collections namespace

วัตถุ IEnumerable แสดงชุดข้อมูลในหน่วยความจำและสามารถย้ายข้อมูลนี้ไปข้างหน้าเท่านั้น แบบสอบถามที่แสดงโดยวัตถุ IEnumerable จะถูกดำเนินการทันทีและครบถ้วนดังนั้นแอปพลิเคชันจะรับข้อมูลอย่างรวดเร็ว

เมื่อมีการดำเนินการค้นหา IEnumerable จะโหลดข้อมูลทั้งหมดและถ้าเราต้องการกรองมันการกรองตัวเองจะทำในฝั่งไคลเอ็นต์

ส่วนติดต่อ IQueryable ตั้งอยู่ใน System.Linq namespace

วัตถุ IQueryable ให้การเข้าถึงระยะไกลไปยังฐานข้อมูลและช่วยให้คุณสามารถนำทางข้อมูลทั้งในลำดับโดยตรงตั้งแต่ต้นจนจบหรือในลำดับย้อนกลับ ในกระบวนการสร้างแบบสอบถามวัตถุที่ส่งคืนคือ IQueryable แบบสอบถามได้รับการปรับให้เหมาะสม เป็นผลให้มีการใช้หน่วยความจำน้อยลงในระหว่างการดำเนินการแบนด์วิธเครือข่ายน้อยลง แต่ในขณะเดียวกันก็สามารถประมวลผลได้ช้ากว่าแบบสอบถามที่ส่งคืนวัตถุ IEnumerable เล็กน้อย

จะเลือกอะไรดี?

หากคุณต้องการข้อมูลที่ส่งคืนทั้งชุดคุณควรใช้ IEnumerable ซึ่งให้ความเร็วสูงสุด

หากคุณไม่ต้องการชุดข้อมูลที่ส่งคืนทั้งหมด แต่มีเพียงข้อมูลที่กรองแล้วบางส่วนคุณควรใช้ IQueryable


0

นอกเหนือจากข้างต้นเป็นที่น่าสนใจที่จะทราบว่าคุณสามารถรับข้อยกเว้นหากคุณใช้IQueryableแทนIEnumerable:

การทำงานต่อไปนี้ใช้ได้ถ้าproductsเป็นIEnumerable:

products.Skip(-4);

อย่างไรก็ตามถ้าproductsเป็นIQueryableและพยายามเข้าถึงบันทึกจากตาราง DB คุณจะได้รับข้อผิดพลาดนี้:

ออฟเซ็ตที่ระบุในส่วน OFFSET อาจไม่เป็นค่าลบ

นี่เป็นเพราะแบบสอบถามต่อไปนี้ถูกสร้างขึ้น:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

และ OFFSET ไม่สามารถมีค่าลบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.