Menggunakan IQueryable dengan Linq

257

Apa gunanya IQueryabledalam konteks LINQ?

Apakah digunakan untuk mengembangkan metode penyuluhan atau tujuan lain?

pengguna190560
sumber

Jawaban:

508

Jawaban Marc Gravell sangat lengkap, tetapi saya pikir saya akan menambahkan sesuatu tentang ini dari sudut pandang pengguna, juga ...


Perbedaan utama, dari perspektif pengguna, adalah bahwa, ketika Anda menggunakan IQueryable<T>(dengan penyedia yang mendukung hal-hal dengan benar), Anda dapat menghemat banyak sumber daya.

Misalnya, jika Anda bekerja melawan basis data jauh, dengan banyak sistem ORM, Anda memiliki opsi untuk mengambil data dari tabel dengan dua cara, yang mengembalikan IEnumerable<T>, dan yang mengembalikan IQueryable<T>. Misalnya, Anda memiliki tabel Produk, dan Anda ingin mendapatkan semua produk yang biayanya> $ 25.

Jika kamu melakukan:

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

Apa yang terjadi di sini, adalah database memuat semua produk, dan meneruskannya ke program Anda. Program Anda kemudian menyaring data. Intinya, database melakukan SELECT * FROM Products, dan mengembalikan setiap produk kepada Anda.

Dengan IQueryable<T>penyedia yang tepat , di sisi lain, Anda dapat melakukan:

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

Kode tersebut terlihat sama, tetapi perbedaannya di sini adalah SQL yang dieksekusi SELECT * FROM Products WHERE Cost >= 25.

Dari POV Anda sebagai pengembang, ini terlihat sama. Namun, dari sudut pandang kinerja, Anda hanya dapat mengembalikan 2 catatan di seluruh jaringan, bukan 20.000 ....

Reed Copsey
sumber
9
Di mana definisi "GetQueryableProducts ();"?
Pankaj
12
@StackOverflowUser Ini dimaksudkan untuk menjadi metode apa pun yang mengembalikan IQueryable<Product>- akan spesifik untuk ORM atau repositori Anda, dll.
Reed Copsey
Baik. tetapi Anda menyebutkan di mana klausa setelah panggilan fungsi ini. Jadi sistem masih tidak mengetahui filter. Maksud saya masih mengambil semua catatan produk. Baik?
Pankaj
@StackOverflowUser Tidak - itulah keindahan IQueryable <T> - ini bisa diatur untuk mengevaluasi ketika Anda mendapatkan hasilnya - yang berarti klausa Di mana, digunakan setelah fakta, masih akan diterjemahkan ke dalam pernyataan SQL yang dijalankan di server, dan tarik hanya elemen yang diperlukan melintasi kawat ...
Reed Copsey
40
@ Pengujian Anda sebenarnya masih tidak pergi ke DB. Sampai Anda benar-benar menghitung hasilnya (yaitu: gunakan a foreach, atau panggilan ToList()), Anda tidak benar-benar menekan DB.
Reed Copsey
188

Pada dasarnya pekerjaannya sangat mirip dengan IEnumerable<T>- untuk mewakili sumber data yang dapat ditanyakan - perbedaannya adalah bahwa berbagai metode LINQ (on Queryable) dapat lebih spesifik, untuk membangun kueri menggunakan Expressionpohon daripada delegasi (yang digunakan adalah apa Enumerable).

Pohon ekspresi dapat diperiksa oleh penyedia LINQ yang Anda pilih dan berubah menjadi permintaan yang sebenarnya - meskipun itu sendiri merupakan seni hitam.

Ini benar-benar ke bawah ElementType, Expressiondan Provider- tetapi dalam kenyataannya Anda jarang perlu peduli tentang ini sebagai pengguna . Hanya pelaksana LINQ yang perlu mengetahui detail berdarah.


Komentar ulang; Saya tidak yakin apa yang Anda inginkan dengan contoh, tetapi pertimbangkan LINQ-to-SQL; objek utama di sini adalah DataContext, yang mewakili pembungkus basis data kami. Ini biasanya memiliki properti per tabel (misalnya, Customers), dan tabel mengimplementasikan IQueryable<Customer>. Tapi kami tidak menggunakan itu secara langsung; mempertimbangkan:

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);
    }
}

ini menjadi (oleh kompiler C #):

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

yang lagi ditafsirkan (oleh kompiler C #) sebagai:

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

Yang penting, metode statis pada Queryablepohon ekspresi mengambil, yang - daripada IL biasa, bisa dikompilasi ke model objek. Misalnya - hanya melihat "Di mana", ini memberi kita sesuatu yang sebanding dengan:

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) ...

Bukankah kompiler melakukan banyak hal untuk kita? Model objek ini dapat dirobek, diperiksa apa artinya, dan disatukan kembali oleh generator TSQL - memberikan sesuatu seperti:

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

(string mungkin berakhir sebagai parameter; Saya tidak ingat)

Semua ini tidak akan mungkin terjadi jika kami baru saja menggunakan delegasi. Dan ini adalah titik Queryable/ IQueryable<T>: ia menyediakan titik masuk untuk menggunakan pohon ekspresi.

Semua ini sangat kompleks, sehingga merupakan pekerjaan yang bagus sehingga kompiler membuatnya bagus dan mudah bagi kami.

Untuk informasi lebih lanjut, lihat " C # in Depth " atau " LINQ in Action ", yang keduanya menyediakan cakupan topik-topik ini.

Marc Gravell
sumber
2
Jika Anda tidak keberatan, dapatkah Anda memperbarui saya dengan contoh sederhana yang dapat dimengerti (jika Anda punya waktu).
user190560
Bisakah Anda menjelaskan "Di mana definisi" GetQueryableProducts (); "?" dalam jawaban Tuan Reed Copsey
Pankaj
Menikmati garis menerjemahkan ekspresi ke kueri adalah "seni hitam itu sendiri" ... banyak kebenaran untuk itu
afreeland
Mengapa tidak ada yang mungkin jika Anda menggunakan delegasi?
David Klempfner
1
@ Backack_Dave karena delegasi menunjuk (pada dasarnya) ke IL, dan IL tidak cukup ekspresif untuk membuatnya masuk akal untuk mencoba mendekonstruksi maksud cukup untuk membangun SQL. IL juga memungkinkan terlalu banyak hal - yaitu kebanyakan hal yang dapat diekspresikan dalam IL tidak dapat diekspresikan dalam sintaks terbatas sehingga masuk akal untuk berubah menjadi hal-hal seperti SQL
Marc Gravell
17

Meskipun Reed Copsey dan Marc Gravell sudah menjelaskan tentang IQueryable(dan juga IEnumerable) cukup, saya ingin menambahkan lebih banyak di sini dengan memberikan contoh kecil IQueryabledan IEnumerablekarena banyak pengguna memintanya

Contoh : Saya telah membuat dua tabel dalam database

   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)

Kunci Utama ( PersonId) dari tabel Employeejuga merupakan kunci forgein ( personid) dari tabelPerson

Selanjutnya saya menambahkan model entitas ado.net dalam aplikasi saya dan membuat kelas layanan di bawah itu

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;
    }
}

mereka berisi LINQ yang sama. Itu disebut program.csseperti yang didefinisikan di bawah ini

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();
    }
}

Outputnya sama untuk keduanya

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

Jadi pertanyaannya adalah apa / di mana bedanya? Sepertinya tidak ada bedanya kan? Betulkah!!

Mari kita lihat query sql yang dihasilkan dan dieksekusi oleh entitas framwork 5 selama periode ini

Bagian eksekusi 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]

Bagian eksekusi 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)

Skrip umum untuk kedua bagian eksekusi

/* 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
*/

Jadi, Anda memiliki beberapa pertanyaan sekarang, izinkan saya menebaknya dan mencoba menjawabnya

Mengapa skrip yang berbeda dihasilkan untuk hasil yang sama?

Mari kita cari beberapa poin di sini,

semua pertanyaan memiliki satu bagian yang sama

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

Mengapa? Karena kedua fungsi IQueryable<Employee> GetEmployeeAndPersonDetailIQueryabledan IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerabledari SomeServiceClassberisi satu baris umum dalam permintaan LINQ

where employeesToCollect.Contains(e.PersonId)

Daripada mengapa AND (N'M' = [Extent1].[Gender])bagian itu hilang di IEnumerablebagian eksekusi, sementara di kedua fungsi memanggil kami menggunakan Where(i => i.Gender == "M") inprogram.cs`

Sekarang kita berada di titik di mana perbedaan terjadi antara IQueryabledan IEnumerable

Apa yang dilakukan kerangka entitas ketika suatu IQueryablemetode dipanggil, ia mengambil pernyataan linq yang ditulis di dalam metode dan mencoba untuk mencari tahu apakah lebih banyak ekspresi linq didefinisikan pada resultset, itu kemudian mengumpulkan semua permintaan linq didefinisikan sampai hasilnya perlu mengambil dan membangun sql yang lebih tepat permintaan untuk dieksekusi.

Ini memberikan banyak manfaat seperti,

  • hanya baris-baris yang diisi oleh sql server yang dapat divalidasi oleh seluruh eksekusi query linq
  • membantu kinerja server sql dengan tidak memilih baris yang tidak perlu
  • mengurangi biaya jaringan

seperti di sini dalam contoh sql server kembali ke aplikasi hanya dua baris setelah eksekusi IQueryable` tetapi mengembalikan TIGA baris untuk permintaan IEnumerable mengapa?

Dalam hal IEnumerablemetode, kerangka kerja mengambil pernyataan LINQ yang ditulis di dalam metode dan membangun kueri sql ketika hasilnya perlu diambil. itu tidak termasuk bagian rest linq untuk membangun kueri sql. Seperti di sini, tidak ada penyaringan yang dilakukan di sql server pada kolom gender.

Tetapi hasilnya sama? Karena 'IEnumerable memfilter hasil lebih lanjut di level aplikasi setelah mengambil hasil dari sql server

JADI, apa yang harus seseorang pilih? Saya pribadi lebih suka mendefinisikan hasil fungsi IQueryable<T>karena ada banyak manfaatnya IEnumerableseperti, Anda bisa bergabung dengan dua atau lebih fungsi IQueryable, yang menghasilkan skrip yang lebih spesifik ke server sql.

Di sini, dalam contoh Anda dapat melihat IQueryable Query(IQueryableQuery2)skrip menghasilkan lebih spesifik daripada IEnumerable query(IEnumerableQuery2)yang jauh lebih dapat diterima menurut saya.

Moumit
sumber
2

Hal ini memungkinkan untuk pertanyaan lebih lanjut di telepon. Jika ini melampaui batas layanan, maka pengguna objek yang IQueryable ini akan diizinkan untuk berbuat lebih banyak dengannya.

Misalnya jika Anda menggunakan lazy loading dengan nhibernate, ini dapat mengakibatkan grafik dimuat ketika / jika diperlukan.

merpati
sumber