Operator Kueri Kompleks

Language Integrated Query (LINQ) berisi banyak operator kompleks, yang menggabungkan beberapa sumber data atau melakukan pemrosesan yang kompleks. Tidak semua operator LINQ memiliki terjemahan yang sesuai di sisi server. Terkadang, kueri dalam satu formulir diterjemahkan ke server tetapi jika ditulis dalam bentuk yang berbeda tidak diterjemahkan meskipun hasilnya sama. Halaman ini menjelaskan beberapa operator kompleks dan variasi yang didukung. Dalam rilis mendatang, kami mungkin mengenali lebih banyak pola dan menambahkan terjemahan yang sesuai. Penting juga untuk diingat bahwa dukungan terjemahan bervariasi di antara penyedia. Kueri tertentu, yang diterjemahkan dalam SqlServer, mungkin tidak berfungsi untuk database SQLite.

Tip

Anda dapat melihat contoh artikel ini di GitHub.

Bergabung

Operator LINQ Join memungkinkan Anda menyambungkan dua sumber data berdasarkan pemilih kunci untuk setiap sumber, menghasilkan tuple nilai saat kunci cocok. Ini secara alami diterjemahkan ke INNER JOIN pada database relasional. Meskipun Gabungan LINQ memiliki pemilih kunci luar dan dalam, database memerlukan satu kondisi gabungan. Jadi EF Core menghasilkan kondisi gabungan dengan membandingkan pemilih kunci luar dengan pemilih kunci dalam untuk kesetaraan.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]

Selanjutnya, jika pemilih kunci adalah jenis anonim, EF Core menghasilkan kondisi gabungan untuk membandingkan kesetaraan komponen-bijaksana.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

GroupJoin

Operator LINQ GroupJoin memungkinkan Anda menyambungkan dua sumber data yang mirip dengan Gabung, tetapi membuat sekelompok nilai dalam untuk mencocokkan elemen luar. Menjalankan kueri seperti contoh berikut menghasilkan hasil Blog & IEnumerable<Post>. Karena database (terutama database relasional) tidak memiliki cara untuk mewakili kumpulan objek sisi klien, GroupJoin tidak diterjemahkan ke server dalam banyak kasus. Ini mengharuskan Anda untuk mendapatkan semua data dari server untuk melakukan GroupJoin tanpa pemilih khusus (kueri pertama di bawah). Tetapi jika pemilih membatasi data yang dipilih, mengambil semua data dari server dapat menyebabkan masalah performa (kueri kedua di bawah). Itulah sebabnya EF Core tidak menerjemahkan GroupJoin.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };

SelectMany

Operator LINQ SelectMany memungkinkan Anda menghitung pemilih koleksi untuk setiap elemen luar dan menghasilkan tuple nilai dari setiap sumber data. Dengan cara, ini adalah gabungan tetapi tanpa kondisi apa pun sehingga setiap elemen luar terhubung dengan elemen dari sumber koleksi. Bergantung pada bagaimana pemilih koleksi terkait dengan sumber data luar, SelectMany dapat diterjemahkan ke dalam berbagai kueri yang berbeda di sisi server.

Pemilih koleksi tidak mereferensikan luar

Ketika pemilih koleksi tidak mereferensikan apa pun dari sumber luar, hasilnya adalah produk kartesius dari kedua sumber data. Ini diterjemahkan ke CROSS JOIN dalam database relasional.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

Pemilih koleksi mereferensikan luar dalam klausa where

Ketika pemilih koleksi memiliki klausa where, yang mereferensikan elemen luar, maka EF Core menerjemahkannya ke gabungan database dan menggunakan predikat sebagai kondisi gabungan. Biasanya kasus ini muncul saat menggunakan navigasi koleksi pada elemen luar sebagai pemilih koleksi. Jika koleksi kosong untuk elemen luar, maka tidak ada hasil yang akan dihasilkan untuk elemen luar tersebut. Tetapi jika DefaultIfEmpty diterapkan pada pemilih koleksi, maka elemen luar akan dihubungkan dengan nilai default elemen dalam. Karena perbedaan ini, kueri semacam ini diterjemahkan dengan INNER JOIN tidak adanya DefaultIfEmpty dan LEFT JOIN kapan DefaultIfEmpty diterapkan.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Pemilih koleksi mereferensikan luar dalam kasus non-di mana

Saat pemilih koleksi mereferensikan elemen luar, yang tidak berada dalam klausa where (seperti kasus di atas), pemilih tersebut tidak diterjemahkan ke gabungan database. Itulah sebabnya kita perlu mengevaluasi pemilih koleksi untuk setiap elemen luar. Ini diterjemahkan ke APPLY operasi dalam banyak database relasional. Jika koleksi kosong untuk elemen luar, maka tidak ada hasil yang akan dihasilkan untuk elemen luar tersebut. Tetapi jika DefaultIfEmpty diterapkan pada pemilih koleksi, maka elemen luar akan dihubungkan dengan nilai default elemen dalam. Karena perbedaan ini, kueri semacam ini diterjemahkan dengan CROSS APPLY tidak adanya DefaultIfEmpty dan OUTER APPLY kapan DefaultIfEmpty diterapkan. Database tertentu seperti SQLite tidak mendukung APPLY operator sehingga kueri semacam ini mungkin tidak diterjemahkan.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

GroupBy

Operator LINQ GroupBy membuat hasil jenis IGrouping<TKey, TElement> di mana TKey dan TElement bisa menjadi jenis arbitrer apa pun. Selain itu, IGrouping mengimplementasikan IEnumerable<TElement>, yang berarti Anda dapat menyusunnya menggunakan operator LINQ apa pun setelah pengelompokan. Karena tidak ada struktur database yang IGroupingdapat mewakili operator , GroupBy tidak memiliki terjemahan dalam banyak kasus. Ketika operator agregat diterapkan ke setiap grup, yang mengembalikan skalar, operator dapat diterjemahkan ke SQL GROUP BY dalam database relasional. SQL GROUP BY juga ketat. Ini mengharuskan Anda untuk mengelompokkan hanya berdasarkan nilai skalar. Proyeksi hanya dapat berisi pengelompokan kolom kunci atau agregat apa pun yang diterapkan di atas kolom. EF Core mengidentifikasi pola ini dan menerjemahkannya ke server, seperti dalam contoh berikut:

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core juga menerjemahkan kueri di mana operator agregat pada pengelompokan muncul di operator LINQ Where atau OrderBy (atau pemesanan lainnya). Ini menggunakan klausa HAVING di SQL untuk klausa where. Bagian dari kueri sebelum menerapkan operator GroupBy bisa menjadi kueri kompleks apa pun selama dapat diterjemahkan ke server. Selain itu, setelah Anda menerapkan operator agregat pada kueri pengelompokan untuk menghapus pengelompokan dari sumber yang dihasilkan, Anda dapat menyusun di atasnya seperti kueri lainnya.

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

Operator agregat yang didukung EF Core adalah sebagai berikut

.NET SQL
Average(x => x.Property) AVG(Property)
Count() COUNT(*)
LongCount() COUNT(*)
Maks(x => x.Property) MAX(Property)
Min(x => x.Property) MIN(Property)
Sum(x => x.Property) SUM(Properti)

Operator agregat tambahan mungkin didukung. Periksa dokumen penyedia Anda untuk pemetaan fungsi lainnya.

Meskipun tidak ada struktur database untuk mewakili IGrouping, dalam beberapa kasus, EF Core 7.0 dan yang lebih baru dapat membuat pengelompokan setelah hasil dikembalikan dari database. Ini mirip dengan cara Include kerja operator saat menyertakan koleksi terkait. Kueri LINQ berikut menggunakan operator GroupBy untuk mengelompokkan hasil berdasarkan nilai properti Harganya.

var query = context.Books.GroupBy(s => s.Price);
SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

Dalam hal ini, operator GroupBy tidak menerjemahkan langsung ke GROUP BY klausul di SQL, tetapi sebaliknya, EF Core membuat pengelompokan setelah hasil dikembalikan dari server.

Gabungan Kiri

Meskipun Gabungan Kiri bukan operator LINQ, database relasional memiliki konsep Gabungan Kiri yang sering digunakan dalam kueri. Pola tertentu dalam kueri LINQ memberikan hasil yang sama seperti LEFT JOIN di server. EF Core mengidentifikasi pola tersebut dan menghasilkan yang setara LEFT JOIN di sisi server. Pola ini melibatkan pembuatan GroupJoin antara kedua sumber data dan kemudian meratakan pengelompokan dengan menggunakan operator SelectMany dengan DefaultIfEmpty pada sumber pengelompokan agar sesuai dengan null ketika bagian dalam tidak memiliki elemen terkait. Contoh berikut menunjukkan seperti apa pola itu dan apa yang dihasilkannya.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Pola di atas membuat struktur kompleks di pohon ekspresi. Karena itu, EF Core mengharuskan Anda untuk meratakan hasil pengelompokan operator GroupJoin dalam langkah segera mengikuti operator. Bahkan jika GroupJoin-DefaultIfEmpty-SelectMany digunakan tetapi dalam pola yang berbeda, kami mungkin tidak mengidentifikasinya sebagai Gabungan Kiri.