ใช้ IQueryable กับ Linq


256

การใช้งานIQueryableในบริบทของ LINQ คืออะไร?

มันใช้สำหรับการพัฒนาวิธีการขยายหรือวัตถุประสงค์อื่น ๆ ?

คำตอบ:


507

คำตอบของ Marc Gravellนั้นสมบูรณ์มาก แต่ฉันคิดว่าฉันจะเพิ่มบางอย่างเกี่ยวกับสิ่งนี้จากมุมมองของผู้ใช้เช่นกัน ...


ความแตกต่างที่สำคัญจากมุมมองของผู้ใช้คือเมื่อคุณใช้IQueryable<T>(กับผู้ให้บริการที่สนับสนุนสิ่งต่าง ๆ อย่างถูกต้อง) คุณสามารถประหยัดทรัพยากรได้มาก

ตัวอย่างเช่นถ้าคุณกำลังทำงานกับฐานข้อมูลระยะไกลด้วยระบบการออมจำนวนมากคุณมีตัวเลือกในการเรียกข้อมูลจากตารางในสองวิธีหนึ่งซึ่งผลตอบแทนและหนึ่งที่ส่งกลับIEnumerable<T> IQueryable<T>ตัวอย่างเช่นคุณมีตารางผลิตภัณฑ์และคุณต้องการรับผลิตภัณฑ์ทั้งหมดที่มีค่าใช้จ่าย> $ 25

ถ้าคุณทำ:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

สิ่งที่เกิดขึ้นที่นี่คือฐานข้อมูลโหลดผลิตภัณฑ์ทั้งหมดและส่งผ่านสายไปยังโปรแกรมของคุณ โปรแกรมของคุณจะกรองข้อมูล ในสาระสำคัญฐานข้อมูลทำSELECT * FROM Productsและส่งคืนสินค้าให้กับคุณ

ในทางIQueryable<T>กลับกันคุณสามารถทำสิ่งต่อไปนี้:

 IQueryable<Product> products = myORM.GetQueryableProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

รหัสลักษณะเดียวกัน แต่แตกต่างกันที่นี่เป็นที่ของ SQL SELECT * FROM Products WHERE Cost >= 25ดำเนินการจะเป็น

จากมุมมองของคุณในฐานะนักพัฒนาดูเหมือนกัน อย่างไรก็ตามจากมุมมองด้านประสิทธิภาพคุณสามารถส่งคืนได้ 2 ระเบียนในเครือข่ายแทนที่จะเป็น 20,000 ....


9
คำจำกัดความของ "GetQueryableProducts ();" อยู่ที่ไหน
Pankaj

12
@StackOverflowUser มันมีจุดประสงค์เพื่อเป็นวิธีการใด ๆ ที่ส่งกลับIQueryable<Product>- จะเฉพาะกับ ORM หรือพื้นที่เก็บข้อมูลของคุณ ฯลฯ
Reed Copsey

ขวา. แต่คุณพูดถึงข้อที่หลังจากฟังก์ชั่นการโทรนี้ ดังนั้นระบบจึงยังไม่รู้ตัวกรอง ฉันหมายความว่ามันยังคงดึงข้อมูลบันทึกทั้งหมดของผลิตภัณฑ์ ขวา?
Pankaj

@StackOverflowUser No - นั่นคือความสวยงามของ IQueryable <T> - มันสามารถตั้งค่าเพื่อประเมินผลเมื่อคุณได้รับผลลัพธ์ - ซึ่งหมายความว่าประโยคไหนที่ใช้หลังจากข้อเท็จจริงจะยังคงได้รับการแปลเป็นคำสั่ง SQL ที่ทำงานบนเซิร์ฟเวอร์และ ดึงเฉพาะองค์ประกอบที่จำเป็นผ่านสาย ...
Reed Copsey

40
@ การทดสอบจริง ๆ แล้วคุณยังไม่ได้ไปที่ฐานข้อมูล จนกว่าคุณจะระบุผลลัพธ์ (เช่น: ใช้foreachหรือโทรToList()) จริง ๆ คุณจะไม่ได้กดฐานข้อมูล
Reed Copsey

188

ในสาระสำคัญหน้าที่ของมันคล้ายกับIEnumerable<T>- เพื่อแสดงแหล่งข้อมูลที่Queryableสามารถสืบค้นได้- ความแตกต่างที่ว่าวิธีการ LINQ (เปิด) ต่างๆนั้นมีความเฉพาะเจาะจงมากขึ้นเพื่อสร้างแบบสอบถามโดยใช้Expressionแผนผังแทนที่จะเป็นตัวแทน (ซึ่งเป็นสิ่งที่Enumerableใช้)

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

นี้เป็นจริงลงไปElementType, ExpressionและProvider- แต่ในความเป็นจริงคุณไม่ค่อยจำเป็นต้องดูแลเกี่ยวกับเรื่องนี้ในฐานะที่เป็นผู้ใช้ ผู้ใช้งาน LINQ เท่านั้นที่ต้องรู้รายละเอียดของเลือด


ความคิดเห็นเรื่อง; ฉันไม่แน่ใจว่าสิ่งที่คุณต้องการเป็นตัวอย่าง แต่พิจารณา LINQ-to-SQL; วัตถุศูนย์กลางนี่คือ a DataContext, ซึ่งหมายถึง wrapper ฐานข้อมูลของเรา นี้มักจะมีคุณสมบัติต่อโต๊ะ (ตัวอย่างCustomers) IQueryable<Customer>และการดำเนินการของตาราง แต่เราไม่ได้ใช้มากโดยตรง พิจารณา:

using(var ctx = new MyDataContext()) {
    var qry = from cust in ctx.Customers
              where cust.Region == "North"
              select new { cust.Id, cust.Name };
    foreach(var row in qry) {
        Console.WriteLine("{0}: {1}", row.Id, row.Name);
    }
}

สิ่งนี้จะกลายเป็น (โดยคอมไพเลอร์ C #):

var qry = ctx.Customers.Where(cust => cust.Region == "North")
                .Select(cust => new { cust.Id, cust.Name });

ซึ่งตีความอีกครั้ง (โดยคอมไพเลอร์ C #) เป็น:

var qry = Queryable.Select(
              Queryable.Where(
                  ctx.Customers,
                  cust => cust.Region == "North"),
              cust => new { cust.Id, cust.Name });

ที่สำคัญวิธีการแบบคงที่ในการQueryableใช้ต้นไม้การแสดงออกซึ่ง - แทนที่จะรวบรวม IL ปกติเพื่อรวบรวมรูปแบบวัตถุ ตัวอย่างเช่น - เพียงดูที่ "ที่ไหน" สิ่งนี้ทำให้เรามีสิ่งที่เทียบเคียงได้กับ:

var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
                  Expression.Equal(
                      Expression.Property(cust, "Region"),
                      Expression.Constant("North")
                  ), cust);

... Queryable.Where(ctx.Customers, lambda) ...

ผู้แปลไม่ได้ทำอะไรมากมายสำหรับเรา แบบจำลองวัตถุนี้สามารถแยกออกจากกันตรวจสอบความหมายและนำกลับมารวมกันอีกครั้งโดยตัวสร้าง TSQL - ให้สิ่งที่ต้องการ:

 SELECT c.Id, c.Name
 FROM [dbo].[Customer] c
 WHERE c.Region = 'North'

(สตริงอาจจบลงด้วยพารามิเตอร์ฉันจำไม่ได้)

สิ่งนี้จะเป็นไปไม่ได้ถ้าเราเพิ่งใช้ตัวแทน และนี่คือจุดQueryable/ IQueryable<T>: มันให้จุดเริ่มต้นสำหรับการใช้ต้นไม้แสดงออก

ทั้งหมดนี้มีความซับซ้อนมากดังนั้นจึงเป็นงานที่ดีที่คอมไพเลอร์ทำให้ดีและง่ายสำหรับเรา

สำหรับข้อมูลเพิ่มเติมดูที่ " C # in Depth " หรือ " LINQ in Action " ซึ่งทั้งสองอย่างนี้ให้ความคุ้มครองหัวข้อเหล่านี้


2
หากคุณไม่รังเกียจคุณสามารถอัปเดตฉันด้วยตัวอย่างที่เข้าใจง่าย (ถ้าคุณมีเวลา)
user190560

คุณช่วยอธิบาย "คำจำกัดความของ" GetQueryableProducts () ที่ไหน; "?" ใน Mr Reed Copsey ตอบ
Pankaj

เพลิดเพลินไปกับบรรทัดของการแปลการแสดงออกไปยังแบบสอบถามคือ "ศิลปะสีดำในตัวเอง" ... ความจริงมากมายกับที่
afreeland

เหตุใดจึงเป็นไปไม่ได้ถ้าคุณใช้ผู้ร่วมประชุม
David Klempfner

1
@Backwards_Dave เนื่องจากผู้รับมอบคะแนน (เป็นหลัก) ถึง IL และ IL ไม่ได้แสดงออกอย่างเพียงพอที่จะทำให้มีเหตุผลในการพยายามแยกแยะเจตนาเพื่อสร้าง SQL อย่างเพียงพอ IL ยังอนุญาตให้มีสิ่งต่าง ๆมากเกินไป - เช่นสิ่งต่าง ๆ ที่สามารถแสดงออกได้ใน IL ไม่สามารถแสดงออกในรูปแบบที่ จำกัด ว่ามันสมเหตุสมผลที่จะเปลี่ยนเป็นสิ่งต่าง ๆ เช่น SQL
Marc Gravell

17

แม้ว่าReed CopseyและMarc Gravellได้อธิบายไว้แล้วเกี่ยวกับIQueryable(และIEnumerable) เพียงพอ mI ต้องการเพิ่มอีกเล็กน้อยที่นี่โดยให้ตัวอย่างเล็ก ๆIQueryableและIEnumerableผู้ใช้หลายคนถาม

ตัวอย่าง : ฉันสร้างสองตารางในฐานข้อมูลแล้ว

   CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
   CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)

คีย์หลัก ( PersonId) ของตารางEmployeeเป็นคีย์ forgein ( personid) ของตารางPerson

ต่อไปฉันได้เพิ่มโมเดลเอนทิตี ado.net ในแอปพลิเคชันของฉันและสร้างบริการด้านล่างนี้

public class SomeServiceClass
{   
    public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }

    public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }
}

พวกเขามี linq เดียวกัน มันเรียกว่าprogram.csตามที่กำหนดไว้ด้านล่าง

class Program
{
    static void Main(string[] args)
    {
        SomeServiceClass s= new SomeServiceClass(); 

        var employeesToCollect= new []{0,1,2,3};

        //IQueryable execution part
        var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");            
        foreach (var emp in IQueryableList)
        {
            System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());

        //IEnumerable execution part
        var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
        foreach (var emp in IEnumerableList)
        {
           System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());

        Console.ReadKey();
    }
}

ผลลัพธ์เหมือนกันทั้งสองอย่างชัดเจน

ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IQueryable contain 2 row in result set  
ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IEnumerable contain 2 row in result set

ดังนั้นคำถามคืออะไรแตกต่างกันที่ไหน? ดูเหมือนว่าจะไม่มีความแตกต่างใช่ไหม? จริงๆ!!

ลองมาดูในการค้นหา sql สร้างและดำเนินการโดยนิติบุคคล framwork 5 ในช่วงเวลาเหล่านี้

ส่วนการดำเนินการ IQueryable

--IQueryableQuery1 
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])

--IQueryableQuery2
SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Employee] AS [Extent1]
    WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
)  AS [GroupBy1]

ส่วนการดำเนินการ IEnumerable

--IEnumerableQuery1
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

--IEnumerableQuery2
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

สคริปต์ทั่วไปสำหรับส่วนการดำเนินการทั้งสอง

/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
   Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1 
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

--ICommonQuery2
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/

ตอนนี้คุณมีคำถามสองสามข้อให้ฉันเดาคำถามเหล่านั้นและพยายามตอบคำถามเหล่านั้น

เหตุใดสคริปต์ต่าง ๆ จึงสร้างขึ้นเพื่อผลลัพธ์เดียวกัน

ช่วยหาจุดที่นี่

ข้อความค้นหาทั้งหมดมีส่วนที่พบบ่อยหนึ่งส่วน

WHERE [Extent1].[PersonId] IN (0,1,2,3)

ทำไม? เพราะทั้งฟังก์ชั่นIQueryable<Employee> GetEmployeeAndPersonDetailIQueryableและ IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableของSomeServiceClassมีหนึ่งบรรทัดทั่วไปในการสืบค้น linq

where employeesToCollect.Contains(e.PersonId)

กว่า AND (N'M' = [Extent1].[Gender])ส่วนที่ขาดหายไปในIEnumerableส่วนของการดำเนินการในขณะที่ทั้งสองฟังก์ชั่นการโทรเราใช้Where(i => i.Gender == "M") inprogram.cs`

ตอนนี้เราอยู่ในจุดที่ความแตกต่างระหว่างIQueryableและ IEnumerable

Framwork เอนทิตีอะไรที่ทำเมื่อIQueryableวิธีการที่เรียกมันใช้คำสั่ง linq เขียนภายในวิธีการและพยายามที่จะหาว่าการแสดงออก linq มากขึ้นมีการกำหนดไว้ใน resultset แล้วมันรวบรวมแบบสอบถามสอบถามทั้งหมด linq ที่กำหนดไว้จนกว่าผลต้องดึงและสร้าง sql ที่เหมาะสมมากขึ้น แบบสอบถามเพื่อดำเนินการ

มันให้ประโยชน์มากมายเช่น

  • เฉพาะแถวเหล่านั้นที่มีเซิร์ฟเวอร์ sql ซึ่งสามารถใช้งานได้โดยการเรียกใช้งานคำสั่ง linq ทั้งหมด
  • ช่วยประสิทธิภาพเซิร์ฟเวอร์ sql โดยไม่เลือกแถวที่ไม่จำเป็น
  • ลดต้นทุนเครือข่าย

เช่นที่นี่ในตัวอย่างเซิร์ฟเวอร์ sql กลับไปที่แอปพลิเคชันเพียงสองแถวหลังจากการดำเนินการ IQueryable` แต่ส่งคืนแถวที่สามสำหรับการค้นหา IEnumerable ทำไม?

ในกรณีของ IEnumerableวิธีการกรอบงานเอนทิตีเอาคำสั่ง linq เขียนไว้ในวิธีการและสร้างแบบสอบถาม SQL เมื่อผลต้องดึง มันไม่รวมส่วนที่เหลือ linq เพื่อสร้างแบบสอบถาม sql เช่นเดียวกับที่นี่ไม่มีการกรองจะทำในเซิร์ฟเวอร์ SQL genderในคอลัมน์

แต่ผลลัพธ์เหมือนกันหรือไม่ เนื่องจาก 'IEnumerable กรองผลลัพธ์เพิ่มเติมในระดับแอปพลิเคชันหลังจากเรียกผลลัพธ์จากเซิร์ฟเวอร์ sql

ดังนั้นสิ่งที่คนควรเลือก? ฉันเองต้องการกำหนดผลลัพธ์ของฟังก์ชันIQueryable<T>เนื่องจากมีประโยชน์มากมายที่มีมากกว่าIEnumerableคุณสามารถเข้าร่วมฟังก์ชัน IQueryable สองฟังก์ชันขึ้นไปซึ่งสร้างสคริปต์ที่เจาะจงมากขึ้นไปยังเซิร์ฟเวอร์ sql

ในตัวอย่างนี้คุณสามารถเห็นการIQueryable Query(IQueryableQuery2)สร้างสคริปต์เฉพาะเจาะจงมากกว่าIEnumerable query(IEnumerableQuery2)ที่ยอมรับได้มากในมุมมองของฉัน


2

จะช่วยให้การสอบถามเพิ่มเติมลงไปที่บรรทัด ถ้าสิ่งนี้อยู่นอกเหนือขอบเขตการให้บริการบอกว่าผู้ใช้ของวัตถุ IQueryable นี้จะได้รับอนุญาตให้ทำอะไรได้มากกว่ากับมัน

ตัวอย่างเช่นหากคุณใช้การโหลดแบบสันหลังยาวกับ nhibernate อาจส่งผลให้กราฟถูกโหลดเมื่อ / หากจำเป็น

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