Bagikan melalui


Menulis kueri C# LINQ untuk mengkueri data

Sebagian besar kueri dalam dokumentasi Kueri Terintegrasi Bahasa (LINQ) pengantar ditulis dengan menggunakan sintaks kueri deklaratif LINQ. Tetapi, sintaks kueri harus diterjemahkan ke dalam panggilan metode untuk runtime bahasa umum (CLR) .NET saat kode dikompilasi. Panggilan metode ini memanggil operator kueri standar, yang memiliki nama seperti Where, Select, GroupBy, Join, Max, dan Average. Anda dapat memanggil operator kueri standar secara langsung dengan menggunakan sintaks metode, bukan sintaks kueri.

Sintaks kueri dan sintaks metode secara semantik identik, tetapi sintaks kueri sering kali lebih sederhana dan lebih mudah dibaca. Beberapa kueri harus diekspresikan sebagai panggilan metode. Misalnya, Anda harus menggunakan panggilan metode untuk mengekspresikan kueri yang mengambil jumlah elemen yang cocok dengan kondisi tertentu. Anda juga harus menggunakan panggilan metode untuk kueri yang mengambil elemen yang memiliki nilai maksimum dalam urutan sumber. Dokumentasi referensi untuk operator kueri standar di namespace System.Linq umumnya menggunakan sintaks metode. Anda harus terbiasa dengan cara menggunakan sintaks metode dalam kueri dan dalam ekspresi kueri itu sendiri.

Metode ekstensi operator kueri standar

Contoh berikut menunjukkan ekspresi kueri sederhana dan kueri yang setara secara semantik yang ditulis sebagai kueri berbasis metode.

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

Output dari dua contoh adalah identik. Anda dapat melihat bahwa jenis variabel kueri sama di kedua bentuk: IEnumerable<T>.

Untuk memahami kueri berbasis metode, mari kita periksa lebih lanjut. Di sisi kanan ekspresi, perhatikan bahwa where klausul sekarang dinyatakan sebagai metode instans pada numbers objek, yang memiliki jenis IEnumerable<int>. Jika Anda terbiasa dengan antarmuka generik IEnumerable<T> , Anda tahu bahwa antarmuka tersebut tidak memiliki Where metode. Namun, jika Anda memanggil daftar penyelesaian IntelliSense di Visual Studio IDE, Anda tidak hanya Where melihat metode, tetapi banyak metode lain seperti Select, SelectMany, Join, dan Orderby. Metode ini mengimplementasikan operator kueri standar.

Cuplikan layar memperlihatkan semua operator kueri standar di Intellisense.

Meskipun terlihat seolah-olah IEnumerable<T> menyertakan lebih banyak metode, itu tidak. Operator kueri standar diimplementasikan sebagai metode ekstensi. Metode ekstensi "memperluas" jenis yang ada; metode ekstensi dapat dipanggil seolah-olah metode ekstensi adalah metode instans pada jenis. Operator kueri standar memperluas IEnumerable<T> dan itulah sebabnya Anda dapat menulis numbers.Where(...).

Untuk menggunakan metode ekstensi, Anda membawanya ke dalam cakupan dengan using direktif. Dari sudut pandang aplikasi Anda, metode ekstensi dan metode instans reguler adalah sama.

Untuk informasi selengkapnya tentang metode ekstensi, lihat Metode Ekstensi. Untuk informasi selengkapnya tentang operator kueri standar, lihat Gambaran Umum Operator Kueri Standar (C#). Beberapa penyedia LINQ, seperti Entity Framework dan LINQ ke XML, menerapkan operator kueri standar dan metode ekstensi mereka sendiri untuk jenis lain selain IEnumerable<T>.

Ekspresi Lambda

Dalam contoh sebelumnya, perhatikan bahwa ekspresi kondisi (num % 2 == 0) diteruskan sebagai argumen in-line ke Enumerable.Where metode : Where(num => num % 2 == 0). Ekspresi sebaris ini adalah ekspresi lambda. Ini adalah cara mudah untuk menulis kode yang sebaliknya harus ditulis dalam bentuk yang lebih rumit. Di num sebelah kiri operator adalah variabel input, yang sesuai dengan num dalam ekspresi kueri. Kompilator dapat menyimpulkan jenis num karena mengetahui bahwa numbers adalah jenis IEnumerable<T> generik. Isi lambda sama dengan ekspresi dalam sintaks kueri atau dalam ekspresi atau pernyataan C# lainnya. Ini dapat mencakup panggilan metode dan logika kompleks lainnya. Nilai yang dikembalikan hanyalah hasil ekspresi. Kueri tertentu hanya dapat diekspresikan dalam sintaks metode dan beberapa di antaranya memerlukan ekspresi lambda. Ekspresi Lambda adalah alat yang kuat dan fleksibel di kotak alat LINQ Anda.

Komposabilitas kueri

Dalam contoh kode sebelumnya, Enumerable.OrderBy metode dipanggil dengan menggunakan operator titik pada panggilan ke Where. Where menghasilkan urutan yang difilter, lalu Orderby mengurutkan urutan yang dihasilkan oleh Where. Karena kueri mengembalikan IEnumerable, Anda menyusunnya dalam sintaks metode dengan menautkan panggilan metode bersama-sama. Pengkompilasi melakukan komposisi ini saat Anda menulis kueri menggunakan sintaks kueri. Karena variabel kueri tidak menyimpan hasil kueri, Anda bisa mengubahnya atau menggunakannya sebagai dasar untuk kueri baru kapan saja, bahkan setelah Anda menjalankannya.

Contoh berikut menunjukkan beberapa kueri LINQ sederhana dengan menggunakan setiap pendekatan yang tercantum sebelumnya.

Catatan

Kueri ini beroperasi pada koleksi dalam memori sederhana; tetapi, sintaks dasar identik dengan yang digunakan di LINQ to Entities dan LINQ to XML.

Contoh - Sintaks kueri

Anda menulis sebagian besar kueri dengan sintaks kueri untuk membuat ekspresi kueri. Contoh berikut menunjukkan tiga ekspresi kueri. Ekspresi kueri pertama menunjukkan cara memfilter atau membatasi hasil dengan menerapkan kondisi dengan klausul where. Ini mengembalikan semua elemen dalam urutan sumber yang nilainya lebih besar dari 7 atau kurang dari 3. Ekspresi kedua menunjukkan cara mengurutkan hasil yang dikembalikan. Ekspresi ketiga menunjukkan cara mengelompokkan hasil sesuai dengan kunci. Kueri ini mengembalikan dua grup berdasarkan huruf pertama dari kata.

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

Jenis kueri adalah IEnumerable<T>. Semua kueri ini dapat ditulis menggunakan var seperti yang ditunjukkan dalam contoh berikut:

var query = from num in numbers...

Dalam setiap contoh sebelumnya, kueri tidak benar-benar dijalankan sampai Anda melakukan iterasi atas variabel kueri dalam foreach pernyataan atau pernyataan lainnya.

Contoh - Sintaks metode

Beberapa operasi kueri harus dinyatakan sebagai panggilan metode. Metode yang paling umum adalah metode yang mengembalikan nilai numerik singleton, seperti , , MaxMin, Averagedan sebagainyaSum. Metode ini harus selalu dipanggil terakhir dalam kueri apa pun karena mengembalikan satu nilai dan tidak dapat berfungsi sebagai sumber untuk operasi kueri tambahan. Contoh berikut menunjukkan pemanggilan metode dalam ekspresi kueri:

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Jika metode memiliki System.Action atau System.Func<TResult> parameter, argumen ini disediakan dalam bentuk ekspresi lambda, seperti yang ditunjukkan dalam contoh berikut:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Dalam kueri sebelumnya, hanya Kueri #4 yang segera dijalankan, karena mengembalikan satu nilai, dan bukan koleksi generik IEnumerable<T> . Metode itu sendiri menggunakan foreach atau kode serupa untuk menghitung nilainya.

Setiap kueri sebelumnya dapat ditulis dengan menggunakan pengetikan implisit dengan 'var'', seperti yang ditunjukkan dalam contoh berikut:

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Contoh - Sintaks kueri dan metode campuran

Contoh ini menunjukkan cara menggunakan sintaks metode pada hasil klausul kueri. Cukup sertakan ekspresi kueri dalam tanda kurung, lalu terapkan operator titik dan panggil metode. Dalam contoh berikut, kueri #7 mengembalikan hitungan angka yang nilainya antara 3 dan 7. Namun, secara umum, lebih baik menggunakan variabel kedua untuk menyimpan hasil panggilan metode. Dengan cara ini, kueri cenderung tidak bingung dengan hasil kueri.

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Karena Kueri #7 mengembalikan nilai tunggal dan bukan kumpulan, kueri segera dieksekusi.

Kueri sebelumnya dapat ditulis menggunakan pengetikan implisit dengan var, sebagai berikut:

var numCount = (from num in numbers...

Ini dapat ditulis dalam sintaks metode sebagai berikut:

var numCount = numbers.Count(n => n is > 3 and < 7);

Hal ini dapat ditulis menggunakan pengetikan eksplisit, sebagai berikut:

int numCount = numbers.Count(n => n is > 3 and < 7);

Tentukan filter predikat secara dinamis saat run time

Dalam beberapa kasus, Anda tidak tahu sampai waktu berjalan berapa banyak predikat yang harus Anda terapkan ke elemen sumber dalam klausa where. Salah satu cara untuk menentukan beberapa filter predikat secara dinamis adalah dengan menggunakan metode Contains, seperti yang ditunjukkan pada contoh berikut. Kueri mengembalikan hasil yang berbeda berdasarkan nilai id saat kueri dijalankan.

int[] ids = [111, 114, 112];

var queryNames =
    from student in students
    where ids.Contains(student.ID)
    select new
    {
        student.LastName,
        student.ID
    };

foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Garcia: 114
    O'Donnell: 112
    Omelchenko: 111
 */

// Change the ids.
ids = [122, 117, 120, 115];

// The query will now return different results
foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Adams: 120
    Feng: 117
    Garcia: 115
    Tucker: 122
 */

Anda dapat menggunakan pernyataan aliran kontrol, sepertiif... else atau switch, untuk memilih di antara kueri alternatif yang telah ditentukan sebelumnya. Dalam contoh berikut, studentQuery menggunakan klausul yang berbeda where jika nilai oddYear run-time adalah true atau false.

void FilterByYearType(bool oddYear)
{
    IEnumerable<Student> studentQuery = oddYear
        ? (from student in students
           where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
           select student)
        : (from student in students
           where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
           select student);
    var descr = oddYear ? "odd" : "even";
    Console.WriteLine($"The following students are at an {descr} year level:");
    foreach (Student name in studentQuery)
    {
        Console.WriteLine($"{name.LastName}: {name.ID}");
    }
}

FilterByYearType(true);

/* Output:
    The following students are at an odd year level:
    Fakhouri: 116
    Feng: 117
    Garcia: 115
    Mortensen: 113
    Tucker: 119
    Tucker: 122
 */

FilterByYearType(false);

/* Output:
    The following students are at an even year level:
    Adams: 120
    Garcia: 114
    Garcia: 118
    O'Donnell: 112
    Omelchenko: 111
    Zabokritski: 121
 */

Menangani nilai null dalam ekspresi kueri

Contoh ini menunjukkan cara menangani kemungkinan nilai null dalam koleksi sumber. Koleksi objek seperti IEnumerable<T> dapat berisi elemen yang nilainya null. Jika kumpulan sumber adalah null atau berisi elemen yang nilainya adalah null, dan kueri Anda tidak menangani null nilai, akan NullReferenceException dilemparkan saat Anda menjalankan kueri.

Anda dapat membuat kode secara defensif untuk menghindari pengecualian referensi null seperti yang ditunjukkan pada contoh berikut:

var query1 =
    from c in categories
    where c != null
    join p in products on c.ID equals p?.CategoryID
    select new
    {
        Category = c.Name,
        Name = p.Name
    };

Pada contoh sebelumnya, klausul where memfilter semua elemen null dalam urutan kategori. Teknik ini tidak bergantung pada pemeriksaan null dalam klausul gabung. Ungkapan bersyarat dengan null dalam contoh ini berfungsi karena Products.CategoryID berjenis int?, yang merupakan singkatan dari Nullable<int>.

Dalam klausul gabung, jika hanya salah satu kunci perbandingan yang merupakan jenis nilai yang dapat diubah ke null, Anda dapat mentransmisikan yang lain ke jenis nilai yang dapat diubah ke null dalam ekspresi kueri. Dalam contoh berikut, asumsikan bahwa EmployeeID adalah kolom yang berisi nilai berjenis int?:

var query =
    from o in db.Orders
    join e in db.Employees
        on o.EmployeeID equals (int?)e.EmployeeID
    select new { o.OrderID, e.FirstName };

Di setiap contoh, kata kunci kueri equals digunakan. Anda juga dapat menggunakan pencocokan pola, yang mencakup pola untuk is null dan is not null. Pola ini tidak disarankan dalam kueri LINQ karena penyedia kueri mungkin tidak menginterpretasikan sintaks C# baru dengan benar. Penyedia kueri adalah pustaka yang menerjemahkan ekspresi kueri C# ke dalam format data native, seperti Entity Framework Core. Penyedia kueri mengimplementasikan antarmuka System.Linq.IQueryProvider untuk membuat sumber data yang mengimplementasikan antarmuka System.Linq.IQueryable<T>.

Menangani pengecualian dalam ekspresi kueri

Anda dapat memanggil metode apa pun dalam konteks ekspresi kueri. Jangan panggil metode apa pun dalam ekspresi kueri yang dapat membuat efek samping seperti memodifikasi konten sumber data atau melemparkan pengecualian. Contoh ini menunjukkan cara menghindari menaikkan pengecualian saat Anda memanggil metode dalam ekspresi kueri tanpa melanggar pedoman .NET umum tentang penanganan pengecualian. Pedoman tersebut menyatakan bahwa dapat diterima untuk menangkap pengecualian tertentu ketika Anda memahami mengapa itu dilemparkan dalam konteks tertentu. Untuk informasi selengkapnya, lihat Praktik Terbaik untuk Pengecualian.

Contoh terakhir menunjukkan cara menangani kasus-kasus tersebut ketika Anda harus memberikan pengecualian selama eksekusi kueri.

Contoh berikut menunjukkan cara memindahkan kode penanganan pengecualian di luar ekspresi kueri. Pemfaktoran ulang ini hanya dimungkinkan ketika metode tidak bergantung pada variabel apa pun yang lokal ke kueri. Lebih mudah untuk menangani pengecualian di luar ekspresi kueri.

// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();

// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
    dataSource = GetData();
}
catch (InvalidOperationException)
{
    Console.WriteLine("Invalid operation");
}

if (dataSource is not null)
{
    // If we get here, it is safe to proceed.
    var query =
        from i in dataSource
        select i * i;

    foreach (var i in query)
    {
        Console.WriteLine(i.ToString());
    }
}

catch (InvalidOperationException) Dalam blok dalam contoh sebelumnya, tangani (atau jangan tangani) pengecualian dengan cara yang sesuai untuk aplikasi Anda.

Dalam beberapa kasus, respons terbaik terhadap pengecualian yang dilemparkan dari dalam kueri mungkin adalah segera menghentikan eksekusi kueri. Contoh berikut menunjukkan cara menangani pengecualian yang mungkin dilemparkan dari dalam badan kueri. Asumsikan bahwa SomeMethodThatMightThrow berpotensi menyebabkan pengecualian yang mengharuskan eksekusi kueri berhenti.

Blok menutupi try perulangan foreach , dan bukan kueri itu sendiri. Perulangan foreach adalah titik di mana kueri dijalankan. Pengecualian run-time dilemparkan saat kueri dijalankan. Oleh karena itu mereka harus ditangani dalam perulangan foreach .

// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
    s[4] == 'C' ?
        throw new InvalidOperationException() :
        @"C:\newFolder\" + s;

// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];

// Demonstration query that throws.
var exceptionDemoQuery =
    from file in files
    let n = SomeMethodThatMightThrow(file)
    select n;

try
{
    foreach (var item in exceptionDemoQuery)
    {
        Console.WriteLine($"Processing {item}");
    }
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message);
}

/* Output:
    Processing C:\newFolder\fileA.txt
    Processing C:\newFolder\fileB.txt
    Operation is not valid due to the current state of the object.
 */

Ingatlah untuk menangkap pengecualian apa pun yang Anda harapkan untuk menaikkan dan/atau melakukan pembersihan yang finally diperlukan di blok.

Lihat juga