Share via


Eksekusi Kueri

Setelah kueri LINQ dibuat oleh pengguna, kueri tersebut diubah menjadi pohon perintah. Pohon perintah adalah representasi dari kueri yang kompatibel dengan Entity Framework. Pohon perintah kemudian dieksekusi terhadap sumber data. Pada waktu eksekusi kueri, semua ekspresi kueri (yaitu, semua komponen kueri) dievaluasi, termasuk ekspresi yang digunakan dalam materialisasi hasil.

Pada titik apa ekspresi kueri dieksekusi dapat bervariasi. Kueri LINQ selalu dieksekusi saat variabel kueri diulang, bukan saat variabel kueri dibuat. Ini disebut eksekusi yang ditangguhkan. Anda juga dapat memaksa kueri untuk segera dieksekusi, yang berguna untuk menyimpan hasil kueri dalam cache. Ini dijelaskan nanti dalam topik ini.

Ketika kueri LINQ to Entities dijalankan, beberapa ekspresi dalam kueri mungkin dieksekusi di server dan beberapa bagian mungkin dieksekusi secara lokal di klien. Evaluasi sisi klien dari ekspresi terjadi sebelum kueri dieksekusi di server. Jika ekspresi dievaluasi pada klien, hasil evaluasi tersebut diganti dengan ekspresi dalam kueri, dan kueri kemudian dieksekusi di server. Karena kueri dijalankan pada sumber data, konfigurasi sumber data menimpa perilaku yang ditentukan di klien. Misalnya, penanganan nilai nol dan presisi numerik bergantung pada pengaturan server. Pengecualian apa pun yang ditampilkan selama eksekusi kueri di server diteruskan langsung ke klien.

Tip

Untuk ringkasan operator kueri yang nyaman dalam format tabel, yang memungkinkan Anda dengan cepat mengidentifikasi perilaku eksekusi operator, lihat Klasifikasi Operator Kueri Standar berdasarkan Cara Eksekusi (C#).

Eksekusi kueri yang ditangguhkan

Dalam kueri yang mengembalikan urutan nilai, variabel kueri itu sendiri tidak pernah menyimpan hasil kueri dan hanya menyimpan perintah kueri. Eksekusi kueri ditunda hingga variabel kueri diulang dalam perulangan foreach atau For Each. Ini dikenal sebagai eksekusi yang ditangguhkan; yaitu, eksekusi kueri terjadi beberapa saat setelah kueri dibuat. Ini berarti Anda dapat menjalankan kueri sesering yang Anda mau. Ini berguna ketika, misalnya, Anda memiliki database yang sedang diperbarui oleh aplikasi lain. Di aplikasi, Anda dapat membuat kueri untuk mengambil informasi terbaru dan berulang kali menjalankan kueri, mengembalikan informasi yang diperbarui setiap saat.

Eksekusi yang ditangguhkan memungkinkan beberapa kueri digabungkan atau kueri diperluas. Ketika diperluas, kueri dimodifikasi untuk memasukkan operasi baru, dan eksekusi akhirnya akan mencerminkan perubahan. Dalam contoh berikut, kueri pertama mengembalikan semua produk. Kueri kedua memperluas yang pertama dengan menggunakan Where untuk mengembalikan semua produk berukuran "L":

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<Product> productsQuery =
        from p in context.Products
        select p;

    IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");

    Console.WriteLine("Products of size 'L':");
    foreach (var product in largeProducts)
    {
        Console.WriteLine(product.Name);
    }
}
Using context As New AdventureWorksEntities()
    Dim productsQuery = _
        From p In context.Products _
        Select p

    Dim largeProducts = _
        productsQuery.Where(Function(p) p.Size = "L")

    Console.WriteLine("Products of size 'L':")
    For Each product In largeProducts
        Console.WriteLine(product.Name)
    Next
End Using

Setelah kueri dieksekusi, semua kueri yang berurutan akan menggunakan operator LINQ dalam memori. Mengulangi variabel kueri dengan menggunakan pernyataan foreach atau For Each atau dengan memanggil salah satu operator konversi LINQ akan menyebabkan eksekusi langsung. Operator konversi ini meliputi: ToList, ToArray, ToLookup, dan ToDictionary.

Eksekusi Kueri Segera

Berbeda dengan eksekusi kueri yang ditangguhkan yang menghasilkan urutan nilai, kueri yang mengembalikan nilai database tunggal akan segera dieksekusi. Beberapa contoh kueri tunggal adalah Average, Count, First, dan Max. Kueri ini segera dieksekusi karena kueri harus menghasilkan urutan untuk menghitung hasil database tunggal. Anda juga dapat memaksa eksekusi segera. Tindakan ini berguna saat Anda ingin menyimpan hasil kueri dalam cache. Untuk memaksa eksekusi segera kueri yang tidak menghasilkan nilai database tunggal, Anda dapat memanggil metode ToList, metode ToDictionary, atau metode ToArray pada kueri atau variabel kueri. Contoh berikut menggunakan metode ToArray untuk segera mengevaluasi urutan ke dalam array.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    ObjectSet<Product> products = context.Products;

    Product[] prodArray = (
        from product in products
        orderby product.ListPrice descending
        select product).ToArray();

    Console.WriteLine("Every price from highest to lowest:");
    foreach (Product product in prodArray)
    {
        Console.WriteLine(product.ListPrice);
    }
}
Using context As New AdventureWorksEntities
    Dim products As ObjectSet(Of Product) = context.Products

    Dim prodArray As Product() = ( _
        From product In products _
        Order By product.ListPrice Descending _
        Select product).ToArray()

    Console.WriteLine("The list price from highest to lowest:")
    For Each prod As Product In prodArray
        Console.WriteLine(prod.ListPrice)
    Next
End Using

Anda juga dapat memaksa eksekusi dengan meletakkan perulangan foreach atau For Each segera setelah ekspresi kueri, tetapi dengan memanggil ToList atau ToArray Anda menyimpan semua data dalam cache objek kumpulan tunggal.

Eksekusi Penyimpanan

Secara umum, ekspresi di LINQ to Entities dievaluasi di server, dan perilaku ekspresi tidak boleh diharapkan mengikuti semantik runtime bahasa umum (CLR), tetapi dari sumber data. Namun, ada pengecualian untuk ini, seperti ketika ekspresi dieksekusi pada klien. Hal ini dapat menyebabkan hasil yang tidak diharapkan, misalnya saat server dan klien berada di zona waktu yang berbeda.

Beberapa ekspresi dalam kueri mungkin dieksekusi pada klien. Secara umum, sebagian besar eksekusi kueri diharapkan terjadi di server. Selain metode yang dieksekusi terhadap elemen kueri yang dipetakan ke sumber data, sering kali ada ekspresi dalam kueri yang dapat dieksekusi secara lokal. Eksekusi lokal dari ekspresi kueri menghasilkan nilai yang dapat digunakan dalam eksekusi kueri atau konstruksi hasil.

Operasi tertentu selalu dijalankan pada klien, seperti pengikatan nilai, sub ekspresi, sub kueri dari penutupan, dan materialisasi objek ke dalam hasil kueri. Efek bersihnya adalah elemen-elemen ini (misalnya, nilai parameter) tidak dapat diperbarui selama eksekusi. Jenis anonim dapat dibangun sebaris pada sumber data, tetapi tidak boleh diasumsikan demikian. Pengelompokan sebaris dapat dibangun di sumber data juga, tetapi ini tidak boleh diasumsikan dalam setiap instans. Secara umum, yang terbaik adalah tidak membuat asumsi tentang apa yang dibangun di server.

Bagian ini menjelaskan skenario di mana kode dijalankan secara lokal pada klien. Untuk informasi selengkapnya tentang jenis ekspresi mana yang dieksekusi secara lokal, lihat Ekspresi dalam Kueri LINQ to Entities.

Literal dan Parameter

Variabel lokal, seperti variabel orderID dalam contoh berikut, dievaluasi pada klien.

int orderID = 51987;

IQueryable<SalesOrderHeader> salesInfo =
    from s in context.SalesOrderHeaders
    where s.SalesOrderID == orderID
    select s;
Dim orderID As Integer = 51987

Dim salesInfo = _
    From s In context.SalesOrderHeaders _
    Where s.SalesOrderID = orderID _
    Select s

Parameter metode juga dievaluasi pada klien. Parameter orderID yang diteruskan ke metode MethodParameterExample, di bawah, adalah contohnya.

public static void MethodParameterExample(int orderID)
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {

        IQueryable<SalesOrderHeader> salesInfo =
            from s in context.SalesOrderHeaders
            where s.SalesOrderID == orderID
            select s;

        foreach (SalesOrderHeader sale in salesInfo)
        {
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
        }
    }
}
Function MethodParameterExample(ByVal orderID As Integer)
    Using context As New AdventureWorksEntities()

        Dim salesInfo = _
            From s In context.SalesOrderHeaders _
            Where s.SalesOrderID = orderID _
            Select s

        Console.WriteLine("Sales order info:")
        For Each sale As SalesOrderHeader In salesInfo
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
        Next
    End Using

End Function

Casting Literal pada Klien

Transmisi dari null ke jenis CLR dijalankan pada klien:

IQueryable<Contact> query =
    from c in context.Contacts
    where c.EmailAddress == (string)null
    select c;
Dim query = _
    From c In context.Contacts _
    Where c.EmailAddress = CType(Nothing, String) _
    Select c

Casting ke jenis, seperti nullable Decimal, dijalankan pada klien:

var weight = (decimal?)23.77;
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = CType(23.77, Decimal?)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Konstruktor untuk Literal

Jenis CLR baru yang dapat dipetakan ke jenis model konseptual dijalankan pada klien:

var weight = new decimal(23.77);
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = New Decimal(23.77)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Array baru juga dieksekusi pada klien.

Pengecualian Penyimpanan

Kesalahan penyimpanan apa pun yang ditemui selama eksekusi kueri diteruskan ke klien, dan tidak dipetakan atau ditangani.

Konfigurasi Penyimpanan

Ketika kueri dijalankan di penyimpanan, konfigurasi penyimpanan menimpa semua perilaku klien, dan semantik penyimpanan diekspresikan untuk semua operasi dan ekspresi. Hal ini dapat mengakibatkan perbedaan perilaku antara CLR dan eksekusi penyimpanan di area seperti perbandingan null, pemesanan GUID, presisi dan akurasi operasi yang melibatkan jenis data yang tidak tepat (seperti jenis titik mengambang atau DateTime), dan operasi string. Penting untuk mengingat hal ini saat memeriksa hasil kueri.

Misalnya, berikut ini adalah beberapa perbedaan perilaku antara CLR dan SQL Server:

  • SQL Server memesan GUID berbeda dari CLR.

  • Ada juga perbedaan dalam presisi hasil ketika berhadapan dengan jenis Desimal di SQL Server. Ini karena persyaratan presisi tetap dari jenis desimal SQL Server. Misalnya, rata-rata nilai Decimal 0,0, 0,0, dan 1,0 adalah 0,3333333333333333333333333333 di memori klien, tetapi 0,333333 di penyimpanan (berdasarkan presisi default untuk jenis desimal SQL Server).

  • Beberapa operasi perbandingan string juga ditangani secara berbeda di SQL Server daripada di CLR. Perilaku perbandingan string tergantung pada pengaturan pemeriksaan di server.

  • Panggilan fungsi atau metode, ketika disertakan dalam kueri LINQ to Entities, dipetakan ke fungsi kanonis dalam Entity Framework, yang kemudian diterjemahkan ke Transact-SQL dan dijalankan di database SQL Server. Ada kasus ketika perilaku yang ditunjukkan oleh fungsi yang dipetakan ini mungkin berbeda dari implementasi di pustaka kelas dasar. Misalnya, memanggil metode Contains, StartsWith, dan EndsWith dengan string kosong sebagai parameter akan mengembalikan true saat dijalankan di CLR, tetapi akan mengembalikan false saat dijalankan di SQL Server. Metode EndsWith juga dapat mengembalikan hasil yang berbeda karena SQL Server menganggap dua string sama jika hanya berbeda dalam white space tambahan, sedangkan CLR menganggapnya tidak sama. Hal ini diilustrasikan oleh contoh berikut:

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<string> query = from p in context.Products
                               where p.Name == "Reflector"
                               select p.Name;

    IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));

    Console.WriteLine("LINQ to Entities returns: " + q.First());
    Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}
Using context As New AdventureWorksEntities()

    Dim query = _
        From p In context.Products _
        Where p.Name = "Reflector" _
        Select p.Name

    Dim q = _
        query.Select(Function(c) c.EndsWith("Reflector "))

    Console.WriteLine("LINQ to Entities returns: " & q.First())
    Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using