Bagikan melalui


Pertimbangan performa untuk EF 4, 5, dan 6

Oleh David Obando, Eric Dettinger dan lainnya

Diterbitkan: April 2012

Terakhir diperbarui: Mei 2014


1. Pendahuluan

Kerangka kerja Pemetaan Relasional Objek adalah cara yang mudah untuk menyediakan abstraksi untuk akses data dalam aplikasi berorientasi objek. Untuk aplikasi .NET, O/RM yang direkomendasikan Microsoft adalah Entity Framework. Namun, dengan abstraksi apa pun, performa dapat menjadi perhatian.

Laporan resmi ini ditulis untuk menunjukkan pertimbangan performa saat mengembangkan aplikasi menggunakan Kerangka Kerja Entitas, untuk memberi pengembang gambaran tentang algoritma internal Kerangka Kerja Entitas yang dapat memengaruhi performa, dan untuk memberikan tips untuk menyelidiki dan meningkatkan performa dalam aplikasi mereka yang menggunakan Kerangka Kerja Entitas. Ada sejumlah topik bagus tentang performa yang sudah tersedia di web, dan kami juga telah mencoba menunjuk ke sumber daya ini jika memungkinkan.

Performa adalah topik yang sulit. Laporan resmi ini dimaksudkan sebagai sumber daya untuk membantu Anda membuat keputusan terkait performa untuk aplikasi Anda yang menggunakan Entity Framework. Kami telah menyertakan beberapa metrik pengujian untuk menunjukkan performa, tetapi metrik ini tidak dimaksudkan sebagai indikator absolut performa yang akan Anda lihat di aplikasi Anda.

Untuk tujuan praktis, dokumen ini mengasumsikan Entity Framework 4 dijalankan di bawah .NET 4.0 dan Entity Framework 5 dan 6 dijalankan di bawah .NET 4.5. Banyak peningkatan performa yang dilakukan untuk Entity Framework 5 berada dalam komponen inti yang dikirim dengan .NET 4.5.

Entity Framework 6 adalah rilis di luar band dan tidak bergantung pada komponen Kerangka Kerja Entitas yang dikirim dengan .NET. Entity Framework 6 berfungsi pada .NET 4.0 dan .NET 4.5, dan dapat menawarkan manfaat performa besar bagi mereka yang belum meningkatkan dari .NET 4.0 tetapi menginginkan bit Kerangka Kerja Entitas terbaru dalam aplikasi mereka. Ketika dokumen ini menyebutkan Entity Framework 6, dokumen ini mengacu pada versi terbaru yang tersedia pada saat penulisan ini: versi 6.1.0.

2. Eksekusi Kueri Dingin vs. Hangat

Pertama kali setiap kueri dibuat terhadap model tertentu, Kerangka Kerja Entitas melakukan banyak pekerjaan di belakang layar untuk memuat dan memvalidasi model. Kami sering menyebut kueri pertama ini sebagai kueri "dingin".  Kueri lebih lanjut terhadap model yang sudah dimuat dikenal sebagai kueri "hangat", dan jauh lebih cepat.

Mari kita ambil tampilan tingkat tinggi di mana waktu dihabiskan saat menjalankan kueri menggunakan Kerangka Kerja Entitas, dan lihat di mana hal-hal yang ditingkatkan dalam Kerangka Kerja Entitas 6.

Eksekusi Kueri Pertama – kueri dingin

Penulisan Pengguna Kode Tindakan Dampak Performa EF4 Dampak Performa EF5 Dampak Performa EF6
using(var db = new MyContext())
{
Pembuatan konteks Medium Medium Kurang Penting
var q1 =
from c in db.Customers
where c.Id == id1
select c;
Pembuatan ekspresi kueri Kurang Penting Kurang Penting Kurang Penting
var c1 = q1.First(); Eksekusi kueri LINQ - Pemuatan metadata: Tinggi tetapi di-cache
- Melihat generasi: Berpotensi sangat tinggi tetapi di-cache
- Evaluasi parameter: Sedang
- Terjemahan kueri: Sedang
- Generasi materializer: Sedang tetapi di-cache
- Eksekusi kueri database: Berpotensi tinggi
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang
- Pencarian identitas: Sedang
- Pemuatan metadata: Tinggi tetapi di-cache
- Melihat generasi: Berpotensi sangat tinggi tetapi di-cache
- Evaluasi parameter: Rendah
- Terjemahan kueri: Sedang tetapi di-cache
- Generasi materializer: Sedang tetapi di-cache
- Eksekusi kueri database: Berpotensi tinggi (Kueri yang lebih baik dalam beberapa situasi)
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang
- Pencarian identitas: Sedang
- Pemuatan metadata: Tinggi tetapi di-cache
- Melihat generasi: Sedang tetapi di-cache
- Evaluasi parameter: Rendah
- Terjemahan kueri: Sedang tetapi di-cache
- Generasi materializer: Sedang tetapi di-cache
- Eksekusi kueri database: Berpotensi tinggi (Kueri yang lebih baik dalam beberapa situasi)
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang (Lebih cepat dari EF5)
- Pencarian identitas: Sedang
} Koneksi ion. Dekat Kurang Penting Kurang Penting Kurang Penting

Eksekusi Kueri Kedua – kueri hangat

Penulisan Pengguna Kode Tindakan Dampak Performa EF4 Dampak Performa EF5 Dampak Performa EF6
using(var db = new MyContext())
{
Pembuatan konteks Medium Medium Kurang Penting
var q1 =
from c in db.Customers
where c.Id == id1
select c;
Pembuatan ekspresi kueri Kurang Penting Kurang Penting Kurang Penting
var c1 = q1.First(); Eksekusi kueri LINQ - Pencarian pemuatan metadata: Tinggi tetapi di-cache Rendah
- Lihat pencarian generasi : Berpotensi sangat tinggi tetapi di-cache Rendah
- Evaluasi parameter: Sedang
- Pencarian terjemahan kueri: Sedang
- Pencarian generasi materializer: Sedang tetapi di-cache Rendah
- Eksekusi kueri database: Berpotensi tinggi
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang
- Pencarian identitas: Sedang
- Pencarian pemuatan metadata: Tinggi tetapi di-cache Rendah
- Lihat pencarian generasi : Berpotensi sangat tinggi tetapi di-cache Rendah
- Evaluasi parameter: Rendah
- Pencarian terjemahan kueri: Sedang tetapi di-cache Rendah
- Pencarian generasi materializer: Sedang tetapi di-cache Rendah
- Eksekusi kueri database: Berpotensi tinggi (Kueri yang lebih baik dalam beberapa situasi)
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang
- Pencarian identitas: Sedang
- Pencarian pemuatan metadata: Tinggi tetapi di-cache Rendah
- Lihat pencarian generasi : Sedang tetapi di-cache Rendah
- Evaluasi parameter: Rendah
- Pencarian terjemahan kueri: Sedang tetapi di-cache Rendah
- Pencarian generasi materializer: Sedang tetapi di-cache Rendah
- Eksekusi kueri database: Berpotensi tinggi (Kueri yang lebih baik dalam beberapa situasi)
+ Koneksi ion. Terbuka
+ Command.ExecuteReader
+ DataReader.Read
Materialisasi objek: Sedang (Lebih cepat dari EF5)
- Pencarian identitas: Sedang
} Koneksi ion. Dekat Kurang Penting Kurang Penting Kurang Penting

Ada beberapa cara untuk mengurangi biaya performa kueri dingin dan hangat, dan kita akan melihat ini di bagian berikut. Secara khusus, kita akan melihat mengurangi biaya pemuatan model dalam kueri dingin dengan menggunakan tampilan yang telah dihasilkan sebelumnya, yang akan membantu meringankan rasa sakit performa yang dialami selama pembuatan tampilan. Untuk kueri hangat, kita akan membahas penembolokan rencana kueri, tidak ada kueri pelacakan, dan opsi eksekusi kueri yang berbeda.

2.1 Apa itu Generasi Tampilan?

Untuk memahami apa itu pembuatan tampilan, kita harus terlebih dahulu memahami apa itu "Tampilan Pemetaan". Tampilan Pemetaan adalah representasi transformasi yang dapat dieksekusi yang ditentukan dalam pemetaan untuk setiap set entitas dan asosiasi. Secara internal, tampilan pemetaan ini berbentuk CQTs (pohon kueri kanonis). Ada dua jenis tampilan pemetaan:

  • Tampilan kueri: ini mewakili transformasi yang diperlukan untuk berubah dari skema database ke model konseptual.
  • Tampilan pembaruan: ini mewakili transformasi yang diperlukan untuk berubah dari model konseptual ke skema database.

Perlu diingat bahwa model konseptual mungkin berbeda dari skema database dengan berbagai cara. Misalnya, satu tabel tunggal dapat digunakan untuk menyimpan data untuk dua jenis entitas yang berbeda. Pemetaan warisan dan non-sepele memainkan peran dalam kompleksitas tampilan pemetaan.

Proses komputasi tampilan ini berdasarkan spesifikasi pemetaan adalah apa yang kita sebut pembuatan tampilan. Pembuatan tampilan dapat berlangsung secara dinamis saat model dimuat, atau pada waktu build, dengan menggunakan "tampilan yang telah dibuat sebelumnya"; yang terakhir diserialisasikan dalam bentuk pernyataan SQL Entitas ke file C# atau VB.

Saat tampilan dihasilkan, tampilan juga divalidasi. Dari sudut pandang performa, sebagian besar biaya pembuatan tampilan sebenarnya adalah validasi tampilan yang memastikan bahwa koneksi antara entitas masuk akal dan memiliki kardinalitas yang benar untuk semua operasi yang didukung.

Saat kueri atas kumpulan entitas dijalankan, kueri dikombinasikan dengan tampilan kueri yang sesuai, dan hasil komposisi ini dijalankan melalui kompilator paket untuk membuat representasi kueri yang dapat dipahami oleh penyimpanan backing. Untuk SQL Server, hasil akhir kompilasi ini akan menjadi pernyataan T-SQL SELECT. Pertama kali pembaruan atas set entitas dilakukan, tampilan pembaruan dijalankan melalui proses serupa untuk mengubahnya menjadi pernyataan DML untuk database target.

2.2 Faktor yang memengaruhi performa Pembuatan Tampilan

Performa langkah pembuatan tampilan tidak hanya tergantung pada ukuran model Anda tetapi juga pada seberapa saling terhubung model. Jika dua Entitas terhubung melalui rantai warisan atau Asosiasi, entitas tersebut dikatakan terhubung. Demikian pula jika dua tabel terhubung melalui kunci asing, tabel tersebut terhubung. Saat jumlah Entitas dan tabel yang terhubung dalam skema Anda meningkat, biaya pembuatan tampilan meningkat.

Algoritma yang kami gunakan untuk menghasilkan dan memvalidasi tampilan bersifat eksponensial dalam kasus terburuk, meskipun kami menggunakan beberapa pengoptimalan untuk meningkatkan ini. Faktor terbesar yang tampaknya berdampak negatif pada performa adalah:

  • Ukuran model, mengacu pada jumlah entitas dan jumlah asosiasi antara entitas ini.
  • Kompleksitas model, khususnya warisan yang melibatkan sejumlah besar jenis.
  • Menggunakan Asosiasi Independen, bukan Asosiasi Kunci Asing.

Untuk model kecil dan sederhana, biayanya mungkin cukup kecil untuk tidak repot-repot menggunakan tampilan yang telah dihasilkan sebelumnya. Ketika ukuran model dan kompleksitas meningkat, ada beberapa opsi yang tersedia untuk mengurangi biaya pembuatan tampilan dan validasi.

2.3 Menggunakan Tampilan yang Telah Dibuat Sebelumnya untuk mengurangi waktu pemuatan model

Untuk informasi terperinci tentang cara menggunakan tampilan yang telah dibuat sebelumnya pada Kerangka Kerja Entitas 6 kunjungi Tampilan Pemetaan yang Telah Dibuat Sebelumnya

2.3.1 Tampilan pra-Hasil menggunakan Entity Framework Power Tools Community Edition

Anda dapat menggunakan Entity Framework 6 Power Tools Community Edition untuk menghasilkan tampilan model EDMX dan Code First dengan mengklik kanan file kelas model dan menggunakan menu Kerangka Kerja Entitas untuk memilih "Hasilkan Tampilan". Entity Framework Power Tools Community Edition hanya berfungsi pada konteks turunan DbContext.

2.3.2 Cara menggunakan tampilan yang dibuat sebelumnya dengan model yang dibuat oleh EDMGen

EDMGen adalah utilitas yang dikirim dengan .NET dan bekerja dengan Entity Framework 4 dan 5, tetapi tidak dengan Entity Framework 6. EDMGen memungkinkan Anda menghasilkan file model, lapisan objek, dan tampilan dari baris perintah. Salah satu output akan menjadi file Tampilan dalam bahasa pilihan Anda, VB atau C#. Ini adalah file kode yang berisi cuplikan Entity SQL untuk setiap set entitas. Untuk mengaktifkan tampilan yang telah dibuat sebelumnya, Anda cukup menyertakan file dalam proyek Anda.

Jika Anda melakukan pengeditan secara manual ke file skema untuk model, Anda harus membuat ulang file tampilan. Anda dapat melakukan ini dengan menjalankan EDMGen dengan bendera /mode:ViewGeneration .

2.3.3 Cara menggunakan Tampilan yang Dibuat Sebelumnya dengan file EDMX

Anda juga dapat menggunakan EDMGen untuk menghasilkan tampilan untuk file EDMX - topik MSDN yang direferensikan sebelumnya menjelaskan cara menambahkan peristiwa pra-build untuk melakukan ini - tetapi ini rumit dan ada beberapa kasus di mana tidak mungkin. Umumnya lebih mudah menggunakan templat T4 untuk menghasilkan tampilan saat model Anda berada dalam file edmx.

Blog tim ADO.NET memiliki postingan yang menjelaskan cara menggunakan templat T4 untuk pembuatan tampilan ( <https://learn.microsoft.com/archive/blogs/adonet/how-to-use-a-t4-template-for-view-generation>). Posting ini menyertakan templat yang dapat diunduh dan ditambahkan ke proyek Anda. Templat ditulis untuk versi pertama Kerangka Kerja Entitas, sehingga tidak dijamin berfungsi dengan versi terbaru Kerangka Kerja Entitas. Namun, Anda dapat mengunduh serangkaian templat pembuatan tampilan yang lebih terbaru untuk Entity Framework 4 dan 5dari Visual Studio Gallery:

  • VB.NET: <http://visualstudiogallery.msdn.microsoft.com/118b44f2-1b91-4de2-a584-7a680418941d>
  • C#: <http://visualstudiogallery.msdn.microsoft.com/ae7730ce-ddab-470f-8456-1b313cd2c44d>

Jika Anda menggunakan Entity Framework 6, Anda bisa mendapatkan templat T4 generasi tampilan dari Visual Studio Gallery di <http://visualstudiogallery.msdn.microsoft.com/18a7db90-6705-4d19-9dd1-0a6c23d0751f>.

2.4 Mengurangi biaya pembuatan tampilan

Menggunakan tampilan yang telah dibuat sebelumnya memindahkan biaya pembuatan tampilan dari pemuatan model (run time) ke waktu desain. Meskipun ini meningkatkan performa startup pada runtime, Anda masih akan mengalami rasa sakit generasi pandang saat Anda mengembangkan. Ada beberapa trik tambahan yang dapat membantu mengurangi biaya pembuatan tampilan, baik pada waktu kompilasi maupun durasi.

2.4.1 Menggunakan Asosiasi Kunci Asing untuk mengurangi biaya pembuatan tampilan

Kami telah melihat sejumlah kasus di mana mengalihkan asosiasi dalam model dari Asosiasi Independen ke Asosiasi Kunci Asing secara dramatis meningkatkan waktu yang dihabiskan dalam pembuatan tampilan.

Untuk menunjukkan peningkatan ini, kami menghasilkan dua versi model Navision dengan menggunakan EDMGen. Catatan: lihat lampiran C untuk deskripsi model Navision. Model Navision menarik untuk latihan ini karena jumlah entitas dan hubungannya yang sangat besar di antara mereka.

Satu versi model yang sangat besar ini dihasilkan dengan Asosiasi Kunci Asing dan yang lain dihasilkan dengan Asosiasi Independen. Kami kemudian menjadwalkan berapa lama waktu yang dibutuhkan untuk menghasilkan tampilan untuk setiap model. Pengujian Entity Framework 5 menggunakan metode GenerateViews() dari kelas EntityViewGenerator untuk menghasilkan tampilan, sementara pengujian Entity Framework 6 menggunakan metode GenerateViews() dari kelas StorageMappingItemCollection. Ini karena restrukturisasi kode yang terjadi di basis kode Entity Framework 6.

Menggunakan Entity Framework 5, lihat pembuatan untuk model dengan Kunci Asing membutuhkan waktu 65 menit di komputer lab. Tidak diketahui berapa lama waktu yang dibutuhkan untuk menghasilkan tampilan untuk model yang menggunakan asosiasi independen. Kami meninggalkan pengujian yang berjalan selama lebih dari sebulan sebelum mesin di-boot ulang di lab kami untuk menginstal pembaruan bulanan.

Menggunakan Entity Framework 6, lihat pembuatan untuk model dengan Kunci Asing membutuhkan waktu 28 detik di komputer lab yang sama. Melihat pembuatan untuk model yang menggunakan Asosiasi Independen membutuhkan waktu 58 detik. Peningkatan yang dilakukan pada Entity Framework 6 pada kode pembuatan tampilannya berarti bahwa banyak proyek tidak akan memerlukan tampilan yang dibuat sebelumnya untuk mendapatkan waktu startup yang lebih cepat.

Penting untuk diberi tahu bahwa tampilan pra-pembuatan di Entity Framework 4 dan 5 dapat dilakukan dengan EDMGen atau Entity Framework Power Tools. Untuk pembuatan tampilan Entity Framework 6 dapat dilakukan melalui Entity Framework Power Tools atau secara terprogram seperti yang dijelaskan dalam Tampilan Pemetaan yang Telah Dibuat Sebelumnya.

2.4.1.1 Cara menggunakan Kunci Asing alih-alih Asosiasi Independen

Saat menggunakan EDMGen atau Perancang Entitas di Visual Studio, Anda mendapatkan FK secara default, dan hanya memerlukan satu kotak centang atau bendera baris perintah untuk beralih antara FK dan IAs.

Jika Anda memiliki model Code First yang besar, menggunakan Asosiasi Independen akan memiliki efek yang sama pada pembuatan tampilan. Anda dapat menghindari dampak ini dengan menyertakan properti Kunci Asing pada kelas untuk objek dependen Anda, meskipun beberapa pengembang akan menganggapnya sebagai mencemari model objek mereka. Anda dapat menemukan informasi selengkapnya tentang subjek ini di <http://blog.oneunicorn.com/2011/12/11/whats-the-deal-with-mapping-foreign-keys-using-the-entity-framework/>.

Saat menggunakan Lakukan ini
Perancang Entitas Setelah menambahkan hubungan antara dua entitas, pastikan Anda memiliki batasan referensial. Batasan referensial memberi tahu Kerangka Kerja Entitas untuk menggunakan Kunci Asing alih-alih Asosiasi Independen. Untuk detail tambahan, kunjungi <https://learn.microsoft.com/archive/blogs/efdesign/foreign-keys-in-the-entity-framework>.
EDMGen Saat menggunakan EDMGen untuk menghasilkan file Anda dari database, Kunci Asing Anda akan dihormati dan ditambahkan ke model seperti itu. Untuk informasi selengkapnya tentang berbagai opsi yang diekspos oleh EDMGen kunjungi http://msdn.microsoft.com/library/bb387165.aspx.
Code First Lihat bagian "Konvensi Hubungan" dari topik Konvensi Pertama Kode untuk informasi tentang cara menyertakan properti kunci asing pada objek dependen saat menggunakan Kode Pertama.

2.4.2 Memindahkan model Anda ke rakitan terpisah

Ketika model Anda disertakan langsung dalam proyek aplikasi Anda dan Anda menghasilkan tampilan melalui peristiwa pra-build atau templat T4, pembuatan dan validasi tampilan akan terjadi setiap kali proyek dibangun kembali, bahkan jika model tidak diubah. Jika Anda memindahkan model ke rakitan terpisah dan mereferensikannya dari proyek aplikasi, Anda dapat membuat perubahan lain pada aplikasi Anda tanpa perlu membangun kembali proyek yang berisi model.

Catatan: saat memindahkan model Anda ke rakitan terpisah ingatlah untuk menyalin string koneksi untuk model ke dalam file konfigurasi aplikasi proyek klien.

2.4.3 Menonaktifkan validasi model berbasis edmx

Model EDMX divalidasi pada waktu kompilasi, bahkan jika model tidak berubah. Jika model Anda telah divalidasi, Anda dapat menekan validasi pada waktu kompilasi dengan mengatur properti "Validasi pada Build" ke false di jendela properti. Saat mengubah pemetaan atau model, Anda dapat mengaktifkan kembali validasi untuk sementara waktu untuk memverifikasi perubahan Anda.

Perhatikan bahwa peningkatan performa dilakukan pada Perancang Kerangka Kerja Entitas untuk Kerangka Kerja Entitas 6, dan biaya "Validasi pada Build" jauh lebih rendah daripada pada versi desainer sebelumnya.

3 Penembolokan dalam Kerangka Kerja Entitas

Entity Framework memiliki bentuk penembolokan bawaan berikut:

  1. Penembolokan objek – ObjectStateManager yang dibangun ke dalam instans ObjectContext melacak memori objek yang telah diambil menggunakan instans tersebut. Ini juga dikenal sebagai cache tingkat pertama.
  2. Penembolokan Rencana Kueri - menggunakan kembali perintah penyimpanan yang dihasilkan saat kueri dijalankan lebih dari sekali.
  3. Penembolokan metadata - berbagi metadata untuk model di berbagai koneksi ke model yang sama.

Selain cache yang disediakan EF di luar kotak, jenis khusus ADO.NET penyedia data yang dikenal sebagai penyedia pembungkus juga dapat digunakan untuk memperluas Kerangka Kerja Entitas dengan cache untuk hasil yang diambil dari database, juga dikenal sebagai penembolokan tingkat kedua.

3.1 Penembolokan Objek

Secara default ketika entitas dikembalikan dalam hasil kueri, tepat sebelum EF mewujudkannya, ObjectContext akan memeriksa apakah entitas dengan kunci yang sama telah dimuat ke dalam ObjectStateManager-nya. Jika entitas dengan kunci yang sama sudah ada, EF akan menyertakannya dalam hasil kueri. Meskipun EF masih akan mengeluarkan kueri terhadap database, perilaku ini dapat melewati banyak biaya materialisasi entitas beberapa kali.

3.1.1 Mendapatkan entitas dari cache objek menggunakan DbContext Find

Tidak seperti kueri biasa, metode Temukan di DbSet (API yang disertakan untuk pertama kalinya di EF 4.1) akan melakukan pencarian dalam memori bahkan sebelum mengeluarkan kueri terhadap database. Penting untuk dicatat bahwa dua instans ObjectContext yang berbeda akan memiliki dua instans ObjectStateManager yang berbeda, yang berarti bahwa instans tersebut memiliki cache objek terpisah.

Temukan menggunakan nilai kunci utama untuk mencoba menemukan entitas yang dilacak oleh konteks. Jika entitas tidak dalam konteks maka kueri akan dijalankan dan dievaluasi terhadap database, dan null dikembalikan jika entitas tidak ditemukan dalam konteks atau dalam database. Perhatikan bahwa Temukan juga mengembalikan entitas yang telah ditambahkan ke konteks tetapi belum disimpan ke database.

Ada pertimbangan performa yang harus diambil saat menggunakan Temukan. Pemanggilan ke metode ini secara default akan memicu validasi cache objek untuk mendeteksi perubahan yang masih menunggu penerapan ke database. Proses ini bisa sangat mahal jika ada sejumlah besar objek dalam cache objek atau dalam grafik objek besar yang ditambahkan ke cache objek, tetapi juga dapat dinonaktifkan. Dalam kasus tertentu, Anda mungkin melihat lebih dari urutan besarnya perbedaan dalam memanggil metode Temukan saat Anda menonaktifkan perubahan deteksi otomatis. Namun urutan kedua besarnya dirasakan ketika objek benar-benar berada di cache versus ketika objek harus diambil dari database. Berikut adalah contoh grafik dengan pengukuran yang diambil menggunakan beberapa tolok ukur mikro kami, yang dinyatakan dalam milidetik, dengan beban 5000 entitas:

.NET 4.5 logarithmic scale

Contoh Temukan dengan perubahan deteksi otomatis dinonaktifkan:

    context.Configuration.AutoDetectChangesEnabled = false;
    var product = context.Products.Find(productId);
    context.Configuration.AutoDetectChangesEnabled = true;
    ...

Apa yang harus Anda pertimbangkan saat menggunakan metode Temukan adalah:

  1. Jika objek tidak berada dalam cache, manfaat Temukan dinegasikan, tetapi sintaksnya masih lebih sederhana daripada kueri menurut kunci.
  2. Jika perubahan deteksi otomatis diaktifkan, biaya metode Temukan dapat meningkat sebesar satu urutan besarnya, atau bahkan lebih tergantung pada kompleksitas model Anda dan jumlah entitas dalam cache objek Anda.

Selain itu, perlu diingat bahwa Find hanya mengembalikan entitas yang Anda cari dan tidak secara otomatis memuat entitas terkait jika belum ada di cache objek. Jika Anda perlu mengambil entitas terkait, Anda dapat menggunakan kueri menurut kunci dengan pemuatan yang bersemangat. Untuk informasi selengkapnya, lihat 8.1 Pemuatan Malas vs. Pemuatan Bersemangat.

3.1.2 Masalah performa ketika cache objek memiliki banyak entitas

Cache objek membantu meningkatkan respons keseluruhan Kerangka Kerja Entitas. Namun, ketika cache objek memiliki sejumlah besar entitas yang dimuat, hal itu dapat memengaruhi operasi tertentu seperti Tambahkan, Hapus, Temukan, Entri, SaveChanges, dan banyak lagi. Secara khusus, operasi yang memicu panggilan ke DetectChanges akan dipengaruhi secara negatif oleh cache objek yang sangat besar. DetectChanges menyinkronkan grafik objek dengan manajer status objek dan performanya akan ditentukan langsung oleh ukuran grafik objek. Untuk informasi selengkapnya tentang DetectChanges, lihat Melacak Perubahan di Entitas POCO.

Saat menggunakan Entity Framework 6, pengembang dapat memanggil AddRange dan RemoveRange langsung di DbSet, alih-alih melakukan iterasi pada koleksi dan memanggil Tambahkan sekali per instans. Keuntungan menggunakan metode rentang adalah bahwa biaya DetectChanges hanya dibayar sekali untuk seluruh set entitas dibandingkan sekali per setiap entitas yang ditambahkan.

3.2 Penembolokan Rencana Kueri

Pertama kali kueri dijalankan, kueri melewati pengkompilasi rencana internal untuk menerjemahkan kueri konseptual ke dalam perintah penyimpanan (misalnya, T-SQL yang dijalankan saat dijalankan terhadap SQL Server).  Jika penembolokan rencana kueri diaktifkan, saat berikutnya kueri dijalankan, perintah penyimpanan diambil langsung dari cache rencana kueri untuk eksekusi, melewati pengkompilasi paket.

Cache paket kueri dibagikan di seluruh instans ObjectContext dalam AppDomain yang sama. Anda tidak perlu menahan instans ObjectContext untuk mendapatkan manfaat dari penembolokan rencana kueri.

3.2.1 Beberapa catatan tentang Penembolokan Rencana Kueri

  • Cache rencana kueri dibagikan untuk semua jenis kueri: Entitas SQL, LINQ ke Entitas, dan objek CompiledQuery.
  • Secara default, penembolokan rencana kueri diaktifkan untuk kueri SQL Entitas, baik dijalankan melalui EntityCommand atau melalui ObjectQuery. Ini juga diaktifkan secara default untuk LINQ ke kueri Entitas dalam Kerangka Kerja Entitas pada .NET 4.5, dan dalam Kerangka Kerja Entitas 6
    • Penembolokan paket kueri dapat dinonaktifkan dengan mengatur properti EnablePlanCaching (di EntityCommand atau ObjectQuery) ke false. Contohnya:
                    var query = from customer in context.Customer
                                where customer.CustomerId == id
                                select new
                                {
                                    customer.CustomerId,
                                    customer.Name
                                };
                    ObjectQuery oQuery = query as ObjectQuery;
                    oQuery.EnablePlanCaching = false;
  • Untuk kueri berparameter, mengubah nilai parameter masih akan mencapai kueri yang di-cache. Tetapi mengubah faset parameter (misalnya, ukuran, presisi, atau skala) akan mencapai entri yang berbeda dalam cache.
  • Saat menggunakan Entity SQL, string kueri adalah bagian dari kunci. Mengubah kueri sama sekali akan menghasilkan entri cache yang berbeda, bahkan jika kueri secara fungsional setara. Ini termasuk perubahan pada casing atau spasi kosong.
  • Saat menggunakan LINQ, kueri diproses untuk menghasilkan bagian dari kunci. Oleh karena itu, mengubah ekspresi LINQ akan menghasilkan kunci yang berbeda.
  • Batasan teknis lainnya dapat berlaku; lihat Kueri kompilasi otomatis untuk detail selengkapnya.

3.2.2 Algoritma pengeluaran cache

Memahami cara kerja algoritma internal akan membantu Anda mencari tahu kapan harus mengaktifkan atau menonaktifkan penembolokan rencana kueri. Algoritma pembersihan adalah sebagai berikut:

  1. Setelah cache berisi sejumlah entri yang ditetapkan (800), kita memulai timer yang secara berkala (sekali per menit) menyapu cache.
  2. Selama pembersihan cache, entri dihapus dari cache berdasarkan LFRU (Paling jarang – baru digunakan). Algoritma ini memperhitungkan hit count dan usia ketika memutuskan entri mana yang dikeluarkan.
  3. Di akhir setiap pembersihan cache, cache lagi berisi 800 entri.

Semua entri cache diperlakukan sama ketika menentukan entri mana yang akan dibuang. Ini berarti perintah penyimpanan untuk CompiledQuery memiliki kemungkinan pengeluaran yang sama dengan perintah penyimpanan untuk kueri Entity SQL.

Perhatikan bahwa timer pengeluaran cache dimulai ketika ada 800 entitas dalam cache, tetapi cache hanya disapu 60 detik setelah timer ini dimulai. Itu berarti bahwa hingga 60 detik cache Anda mungkin tumbuh cukup besar.

3.2.3 Metrik Pengujian yang menunjukkan performa penembolokan rencana kueri

Untuk menunjukkan efek penembolokan rencana kueri pada performa aplikasi Anda, kami melakukan pengujian di mana kami menjalankan sejumlah kueri Entity SQL terhadap model Navision. Lihat lampiran untuk deskripsi model Navision dan jenis kueri yang dijalankan. Dalam pengujian ini, pertama-tama kita melakukan iterasi melalui daftar kueri dan menjalankan masing-masing sekali untuk menambahkannya ke cache (jika penembolokan diaktifkan). Langkah ini tidak waktunya. Selanjutnya, kita tidur utas utama selama lebih dari 60 detik untuk memungkinkan pembersihan cache berlangsung; akhirnya, kami melakukan iterasi melalui daftar ke-2 kalinya untuk menjalankan kueri yang di-cache. Selain itu, cache paket SQL Server dihapus sebelum setiap set kueri dijalankan sehingga waktu yang kita peroleh secara akurat mencerminkan manfaat yang diberikan oleh cache rencana kueri.

3.2.3.1 Hasil Pengujian
Uji EF5 tanpa cache EF5 di-cache EF6 tanpa cache EF6 di-cache
Menghitung semua kueri 18723 124 125,4 124,3 125.3
Menghindari pembersihan (hanya 800 kueri pertama, terlepas dari kompleksitas) 41.7 5.5 40,5 5.4
Hanya kueri AggregatingSubtotals (total 178 - yang menghindari pembersihan) 39,5 4.5 38,1 4.6

Setiap kali dalam detik.

Moral - saat mengeksekusi banyak kueri yang berbeda (misalnya, kueri yang dibuat secara dinamis), penembolokan tidak membantu dan pembilasan cache yang dihasilkan dapat menjaga kueri yang paling menguntungkan dari penembolokan rencana agar tidak benar-benar menggunakannya.

Kueri AggregatingSubtotals adalah kueri paling kompleks yang kami uji. Seperti yang diharapkan, semakin kompleks kueri, semakin banyak manfaat yang akan Anda lihat dari penembolokan rencana kueri.

Karena CompiledQuery benar-benar kueri LINQ dengan paketnya di-cache, perbandingan CompiledQuery versus kueri SQL Entitas yang setara harus memiliki hasil yang sama. Bahkan, jika aplikasi memiliki banyak kueri SQL Entitas dinamis, mengisi cache dengan kueri juga akan secara efektif menyebabkan CompiledQueries "dekompilasi" ketika dihapus dari cache. Dalam skenario ini, performa dapat ditingkatkan dengan menonaktifkan penembolokan pada kueri dinamis untuk memprioritaskan CompiledQueries. Lebih baik lagi, tentu saja, akan menulis ulang aplikasi untuk menggunakan kueri berparameter alih-alih kueri dinamis.

3.3 Menggunakan CompiledQuery untuk meningkatkan performa dengan kueri LINQ

Pengujian kami menunjukkan bahwa menggunakan CompiledQuery dapat membawa manfaat 7% daripada kueri LINQ yang dikompilasi otomatis; ini berarti Anda akan menghabiskan 7% lebih sedikit waktu untuk mengeksekusi kode dari tumpukan Kerangka Kerja Entitas; itu tidak berarti aplikasi Anda akan menjadi 7% lebih cepat. Secara umum, biaya penulisan dan pemeliharaan objek CompiledQuery di EF 5.0 mungkin tidak sebanding dengan masalah jika dibandingkan dengan manfaatnya. Jarak tempuh Anda dapat bervariasi, jadi gunakan opsi ini jika proyek Anda memerlukan dorongan ekstra. Perhatikan bahwa CompiledQueries hanya kompatibel dengan model turunan ObjectContext, dan tidak kompatibel dengan model turunan DbContext.

Untuk informasi selengkapnya tentang membuat dan memanggil CompiledQuery, lihat Kueri yang Dikompilasi (LINQ ke Entitas).

Ada dua pertimbangan yang harus Anda ambil saat menggunakan CompiledQuery, yaitu persyaratan untuk menggunakan instans statis dan masalah yang mereka miliki dengan komposabilitas. Berikut ini mengikuti penjelasan mendalam tentang dua pertimbangan ini.

3.3.1 Menggunakan instans CompiledQuery statis

Karena mengkompilasi kueri LINQ adalah proses yang memakan waktu, kita tidak ingin melakukannya setiap kali kita perlu mengambil data dari database. Instans CompiledQuery memungkinkan Anda untuk mengkompilasi sekali dan menjalankan beberapa kali, tetapi Anda harus berhati-hati dan mendapatkan untuk menggunakan kembali instans CompiledQuery yang sama setiap kali alih-alih mengkompilasinya berulang-ulang. Penggunaan anggota statis untuk menyimpan instans CompiledQuery menjadi diperlukan; jika tidak, Anda tidak akan melihat manfaat apa pun.

Misalnya, halaman Anda memiliki isi metode berikut untuk menangani menampilkan produk untuk kategori yang dipilih:

    // Warning: this is the wrong way of using CompiledQuery
    using (NorthwindEntities context = new NorthwindEntities())
    {
        string selectedCategory = this.categoriesList.SelectedValue;

        var productsForCategory = CompiledQuery.Compile<NorthwindEntities, string, IQueryable<Product>>(
            (NorthwindEntities nwnd, string category) =>
                nwnd.Products.Where(p => p.Category.CategoryName == category)
        );

        this.productsGrid.DataSource = productsForCategory.Invoke(context, selectedCategory).ToList();
        this.productsGrid.DataBind();
    }

    this.productsGrid.Visible = true;

Dalam hal ini, Anda akan membuat instans CompiledQuery baru dengan cepat setiap kali metode dipanggil. Alih-alih melihat manfaat performa dengan mengambil perintah penyimpanan dari cache rencana kueri, CompiledQuery akan melalui kompilator paket setiap kali instans baru dibuat. Bahkan, Anda akan mencemari cache rencana kueri Anda dengan entri CompiledQuery baru setiap kali metode dipanggil.

Sebagai gantinya, Anda ingin membuat instans statis kueri yang dikompilasi, sehingga Anda memanggil kueri yang dikompilasi yang sama setiap kali metode dipanggil. Salah satu caranya adalah dengan menambahkan instans CompiledQuery sebagai anggota konteks objek Anda.  Anda kemudian dapat membuat hal-hal sedikit lebih bersih dengan mengakses CompiledQuery melalui metode pembantu:

    public partial class NorthwindEntities : ObjectContext
    {
        private static readonly Func<NorthwindEntities, string, IEnumerable<Product>> productsForCategoryCQ = CompiledQuery.Compile(
            (NorthwindEntities context, string categoryName) =>
                context.Products.Where(p => p.Category.CategoryName == categoryName)
            );

        public IEnumerable<Product> GetProductsForCategory(string categoryName)
        {
            return productsForCategoryCQ.Invoke(this, categoryName).ToList();
        }

Metode pembantu ini akan dipanggil sebagai berikut:

    this.productsGrid.DataSource = context.GetProductsForCategory(selectedCategory);

3.3.2 Menyusun compiledQuery

Kemampuan untuk menyusun kueri LINQ apa pun sangat berguna; untuk melakukan ini, Anda cukup memanggil metode setelah IQueryable seperti Skip() atau Count(). Namun, melakukannya pada dasarnya mengembalikan objek IQueryable baru. Meskipun tidak ada yang menghentikan Anda secara teknis untuk menyusun CompiledQuery, melakukannya akan menyebabkan pembuatan objek IQueryable baru yang mengharuskan melewati kompilator rencana lagi.

Beberapa komponen akan menggunakan objek IQueryable yang terdiri untuk mengaktifkan fungsionalitas tingkat lanjut. Misalnya, GridView ASP.NET dapat terikat data ke objek IQueryable melalui properti SelectMethod. GridView kemudian akan menyusun objek IQueryable ini untuk memungkinkan pengurutan dan penomoran melalui model data. Seperti yang Anda lihat, menggunakan CompiledQuery untuk GridView tidak akan mencapai kueri yang dikompilasi tetapi akan menghasilkan kueri kompilasi otomatis baru.

Satu tempat di mana Anda mungkin mengalami hal ini adalah saat menambahkan filter progresif ke kueri. Misalnya, Anda memiliki halaman Pelanggan dengan beberapa daftar drop-down untuk filter opsional (misalnya, Country dan OrdersCount). Anda dapat menyusun filter ini melalui hasil IQueryable dari CompiledQuery, tetapi melakukannya akan mengakibatkan kueri baru melalui pengkompilasi paket setiap kali Anda menjalankannya.

    using (NorthwindEntities context = new NorthwindEntities())
    {
        IQueryable<Customer> myCustomers = context.InvokeCustomersForEmployee();

        if (this.orderCountFilterList.SelectedItem.Value != defaultFilterText)
        {
            int orderCount = int.Parse(orderCountFilterList.SelectedValue);
            myCustomers = myCustomers.Where(c => c.Orders.Count > orderCount);
        }

        if (this.countryFilterList.SelectedItem.Value != defaultFilterText)
        {
            myCustomers = myCustomers.Where(c => c.Address.Country == countryFilterList.SelectedValue);
        }

        this.customersGrid.DataSource = myCustomers;
        this.customersGrid.DataBind();
    }

 Untuk menghindari kompilasi ulang ini, Anda dapat menulis ulang CompiledQuery untuk memperhitungkan kemungkinan filter:

    private static readonly Func<NorthwindEntities, int, int?, string, IQueryable<Customer>> customersForEmployeeWithFiltersCQ = CompiledQuery.Compile(
        (NorthwindEntities context, int empId, int? countFilter, string countryFilter) =>
            context.Customers.Where(c => c.Orders.Any(o => o.EmployeeID == empId))
            .Where(c => countFilter.HasValue == false || c.Orders.Count > countFilter)
            .Where(c => countryFilter == null || c.Address.Country == countryFilter)
        );

Yang akan dipanggil di UI seperti:

    using (NorthwindEntities context = new NorthwindEntities())
    {
        int? countFilter = (this.orderCountFilterList.SelectedIndex == 0) ?
            (int?)null :
            int.Parse(this.orderCountFilterList.SelectedValue);

        string countryFilter = (this.countryFilterList.SelectedIndex == 0) ?
            null :
            this.countryFilterList.SelectedValue;

        IQueryable<Customer> myCustomers = context.InvokeCustomersForEmployeeWithFilters(
                countFilter, countryFilter);

        this.customersGrid.DataSource = myCustomers;
        this.customersGrid.DataBind();
    }

 Tradeoff di sini adalah perintah penyimpanan yang dihasilkan akan selalu memiliki filter dengan pemeriksaan null, tetapi ini seharusnya cukup sederhana bagi server database untuk mengoptimalkan:

...
WHERE ((0 = (CASE WHEN (@p__linq__1 IS NOT NULL) THEN cast(1 as bit) WHEN (@p__linq__1 IS NULL) THEN cast(0 as bit) END)) OR ([Project3].[C2] > @p__linq__2)) AND (@p__linq__3 IS NULL OR [Project3].[Country] = @p__linq__4)

Penembolokan metadata 3.4

Kerangka Kerja Entitas juga mendukung penembolokan Metadata. Ini pada dasarnya adalah penembolokan informasi jenis dan informasi pemetaan jenis-ke-database di berbagai koneksi ke model yang sama. Cache Metadata unik per AppDomain.

3.4.1 Algoritma Penembolokan Metadata

  1. Informasi metadata untuk model disimpan dalam ItemCollection untuk setiap Entitas Koneksi ion.

    • Sebagai catatan samping, ada objek ItemCollection yang berbeda untuk berbagai bagian model. Misalnya, StoreItemCollections berisi informasi tentang model database; ObjectItemCollection berisi informasi tentang model data; EdmItemCollection berisi informasi tentang model konseptual.
  2. Jika dua koneksi menggunakan string koneksi yang sama, mereka akan berbagi instans ItemCollection yang sama.

  3. Secara fungsional setara tetapi string koneksi yang berbeda secara tekstual dapat mengakibatkan cache metadata yang berbeda. Kami melakukan tokenisasi string koneksi, jadi hanya mengubah urutan token akan mengakibatkan metadata bersama. Tetapi dua string koneksi yang tampaknya secara fungsional sama mungkin tidak dievaluasi sebagai identik setelah tokenisasi.

  4. ItemCollection secara berkala diperiksa untuk digunakan. Jika ditentukan bahwa ruang kerja belum diakses baru-baru ini, ruang kerja tersebut akan ditandai untuk pembersihan pada pembersihan cache berikutnya.

  5. Hanya membuat Entitas Koneksi ion akan menyebabkan cache metadata dibuat (meskipun koleksi item di dalamnya tidak akan diinisialisasi hingga koneksi dibuka). Ruang kerja ini akan tetap dalam memori sampai algoritma penembolokan menentukan bahwa ruang kerja tersebut tidak "digunakan".

Tim Penasihat Pelanggan telah menulis posting blog yang menjelaskan memegang referensi ke ItemCollection untuk menghindari "penghentian" saat menggunakan model besar: <https://learn.microsoft.com/archive/blogs/appfabriccat/holding-a-reference-to-the-ef-metadataworkspace-for-wcf-services>.

3.4.2 Hubungan antara Penembolokan Metadata dan Penembolokan Rencana Kueri

Instans cache paket kueri berada di Jenis penyimpanan ItemCollection MetadataWorkspace. Ini berarti bahwa perintah penyimpanan yang di-cache akan digunakan untuk kueri terhadap konteks apa pun yang dibuat menggunakan MetadataWorkspace tertentu. Ini juga berarti bahwa jika Anda memiliki dua string koneksi yang sedikit berbeda dan tidak cocok setelah tokenisasi, Anda akan memiliki instans cache rencana kueri yang berbeda.

3.5 Penembolokan hasil

Dengan penembolokan hasil (juga dikenal sebagai "penembolokan tingkat kedua"), Anda menyimpan hasil kueri di cache lokal. Saat mengeluarkan kueri, Anda terlebih dahulu melihat apakah hasilnya tersedia secara lokal sebelum Anda mengkueri terhadap penyimpanan. Meskipun penembolokan hasil tidak didukung langsung oleh Kerangka Kerja Entitas, dimungkinkan untuk menambahkan cache tingkat kedua dengan menggunakan penyedia pembungkus. Contoh penyedia pembungkusan dengan cache tingkat kedua adalah Cache Tingkat Kedua Kerangka Kerja Entitas Alachisoft berdasarkan NCache.

Implementasi penembolokan tingkat kedua ini adalah fungsionalitas yang disuntikkan yang terjadi setelah ekspresi LINQ dievaluasi (dan disalurkan) dan rencana eksekusi kueri dihitung atau diambil dari cache tingkat pertama. Cache tingkat kedua kemudian hanya akan menyimpan hasil database mentah, sehingga alur materialisasi masih dijalankan setelahnya.

3.5.1 Referensi tambahan untuk penembolokan hasil dengan penyedia pembungkus

  • Julie Lerman telah menulis artikel MSDN "Penembolokan Tingkat Kedua dalam Kerangka Kerja Entitas dan Windows Azure" yang menyertakan cara memperbarui penyedia pembungkus sampel untuk menggunakan penembolokan Windows Server AppFabric: https://msdn.microsoft.com/magazine/hh394143.aspx
  • Jika Anda bekerja dengan Entity Framework 5, blog tim memiliki postingan yang menjelaskan cara menjalankan hal-hal dengan penyedia penembolokan untuk Entity Framework 5: <https://learn.microsoft.com/archive/blogs/adonet/ef-caching-with-jarek-kowalskis-provider>. Ini juga termasuk templat T4 untuk membantu mengotomatiskan penambahan penembolokan tingkat ke-2 ke proyek Anda.

4 Kueri yang Dikompresi Otomatis

Ketika kueri dikeluarkan terhadap database menggunakan Kerangka Kerja Entitas, kueri harus melalui serangkaian langkah sebelum benar-benar mewujudkan hasil; salah satu langkah tersebut adalah Kompilasi Kueri. Kueri SQL entitas diketahui memiliki performa yang baik karena secara otomatis di-cache, sehingga kedua atau ketiga kalinya Anda menjalankan kueri yang sama, kueri tersebut dapat melewati pengkompilasi paket dan menggunakan paket yang di-cache sebagai gantinya.

Entity Framework 5 memperkenalkan penembolokan otomatis untuk kueri LINQ ke Entitas juga. Dalam edisi Entity Framework sebelumnya yang membuat CompiledQuery untuk mempercepat performa Anda adalah praktik umum, karena ini akan membuat kueri LINQ ke Entitas Anda dapat di-cache. Karena penembolokan sekarang dilakukan secara otomatis tanpa menggunakan CompiledQuery, kami menyebut fitur ini "kueri yang dikompilasi otomatis". Untuk informasi selengkapnya tentang cache rencana kueri dan mekanismenya, lihat Penembolokan Rencana Kueri.

Kerangka Kerja Entitas mendeteksi kapan kueri harus dikompilasi ulang, dan melakukannya ketika kueri dipanggil bahkan jika telah dikompilasi sebelumnya. Kondisi umum yang menyebabkan kueri dikompresi ulang adalah:

  • Mengubah MergeOption yang terkait dengan kueri Anda. Kueri yang di-cache tidak akan digunakan, sebaliknya pengkompilasi paket akan berjalan lagi dan paket yang baru dibuat akan di-cache.
  • Mengubah nilai ContextOptions.UseCSharpNullComparisonBehavior. Anda mendapatkan efek yang sama dengan mengubah MergeOption.

Kondisi lain dapat mencegah kueri Anda menggunakan cache. Contoh umumnya adalah:

  • Menggunakan IEnumerable<T>. Berisi<>(nilai T).
  • Menggunakan fungsi yang menghasilkan kueri dengan konstanta.
  • Menggunakan properti objek yang tidak dipetakan.
  • Menautkan kueri Anda ke kueri lain yang memerlukan kompilasi ulang.

4.1 Menggunakan IEnumerable<T>. Berisi<nilai T>(T)

Kerangka Kerja Entitas tidak menyimpan kueri yang memanggil IEnumerable<T>. Berisi<nilai T>(T) terhadap koleksi dalam memori, karena nilai koleksi dianggap volatil. Contoh kueri berikut tidak akan di-cache, sehingga akan selalu diproses oleh pengkompilasi paket:

int[] ids = new int[10000];
...
using (var context = new MyContext())
{
    var query = context.MyEntities
                    .Where(entity => ids.Contains(entity.Id));

    var results = query.ToList();
    ...
}

Perhatikan bahwa ukuran IEnumerable yang berisi dijalankan menentukan seberapa cepat atau seberapa lambat kueri Anda dikompilasi. Performa dapat menderita secara signifikan saat menggunakan koleksi besar seperti yang ditunjukkan pada contoh di atas.

Entity Framework 6 berisi pengoptimalan dengan cara IEnumerable<T>. Berisi<nilai T>(T) berfungsi saat kueri dijalankan. Kode SQL yang dihasilkan jauh lebih cepat untuk menghasilkan dan lebih mudah dibaca, dan dalam banyak kasus juga dijalankan lebih cepat di server.

4.2 Menggunakan fungsi yang menghasilkan kueri dengan konstanta

Operator LINQ Skip(), Take(), Contains() dan DefautIfEmpty() tidak menghasilkan kueri SQL dengan parameter tetapi sebaliknya menempatkan nilai yang diteruskan ke mereka sebagai konstanta. Karena itu, kueri yang mungkin identik akhirnya mencemari cache rencana kueri, baik di tumpukan EF maupun di server database, dan tidak dimanfaatkan kembali kecuali konstanta yang sama digunakan dalam eksekusi kueri berikutnya. Contohnya:

var id = 10;
...
using (var context = new MyContext())
{
    var query = context.MyEntities.Select(entity => entity.Id).Contains(id);

    var results = query.ToList();
    ...
}

Dalam contoh ini, setiap kali kueri ini dijalankan dengan nilai yang berbeda untuk id, kueri akan dikompilasi ke dalam paket baru.

Secara khusus perhatikan penggunaan Lewati dan Ambil saat melakukan paging. Dalam EF6 metode ini memiliki kelebihan beban lambda yang secara efektif membuat rencana kueri yang di-cache dapat digunakan kembali karena EF dapat menangkap variabel yang diteruskan ke metode ini dan menerjemahkannya ke SQLparameters. Ini juga membantu menjaga pembersih cache karena jika tidak, setiap kueri dengan konstanta yang berbeda untuk Lewati dan Ambil akan mendapatkan entri cache rencana kuerinya sendiri.

Pertimbangkan kode berikut, yang suboptimal tetapi hanya dimaksudkan untuk mencontohkan kelas kueri ini:

var customers = context.Customers.OrderBy(c => c.LastName);
for (var i = 0; i < count; ++i)
{
    var currentCustomer = customers.Skip(i).FirstOrDefault();
    ProcessCustomer(currentCustomer);
}

Versi yang lebih cepat dari kode yang sama ini akan melibatkan panggilan Lewati dengan lambda:

var customers = context.Customers.OrderBy(c => c.LastName);
for (var i = 0; i < count; ++i)
{
    var currentCustomer = customers.Skip(() => i).FirstOrDefault();
    ProcessCustomer(currentCustomer);
}

Cuplikan kedua dapat berjalan hingga 11% lebih cepat karena rencana kueri yang sama digunakan setiap kali kueri dijalankan, yang menghemat waktu CPU dan menghindari pencemaian cache kueri. Selain itu, karena parameter yang akan Dilewati berada dalam penutupan kode mungkin juga terlihat seperti ini sekarang:

var i = 0;
var skippyCustomers = context.Customers.OrderBy(c => c.LastName).Skip(() => i);
for (; i < count; ++i)
{
    var currentCustomer = skippyCustomers.FirstOrDefault();
    ProcessCustomer(currentCustomer);
}

4.3 Menggunakan properti objek yang tidak dipetakan

Saat kueri menggunakan properti jenis objek yang tidak dipetakan sebagai parameter, kueri tidak akan di-cache. Contohnya:

using (var context = new MyContext())
{
    var myObject = new NonMappedType();

    var query = from entity in context.MyEntities
                where entity.Name.StartsWith(myObject.MyProperty)
                select entity;

   var results = query.ToList();
    ...
}

Dalam contoh ini, asumsikan bahwa kelas NonMappedType bukan bagian dari model Entitas. Kueri ini dapat dengan mudah diubah untuk tidak menggunakan jenis yang tidak dipetakan dan sebaliknya menggunakan variabel lokal sebagai parameter ke kueri:

using (var context = new MyContext())
{
    var myObject = new NonMappedType();
    var myValue = myObject.MyProperty;
    var query = from entity in context.MyEntities
                where entity.Name.StartsWith(myValue)
                select entity;

    var results = query.ToList();
    ...
}

Dalam hal ini, kueri akan dapat di-cache dan akan mendapat manfaat dari cache rencana kueri.

4.4 Menautkan ke kueri yang memerlukan kompilasi ulang

Mengikuti contoh yang sama seperti di atas, jika Anda memiliki kueri kedua yang bergantung pada kueri yang perlu dikompresi ulang, seluruh kueri kedua Anda juga akan dikompresi ulang. Berikut adalah contoh untuk mengilustrasikan skenario ini:

int[] ids = new int[10000];
...
using (var context = new MyContext())
{
    var firstQuery = from entity in context.MyEntities
                        where ids.Contains(entity.Id)
                        select entity;

    var secondQuery = from entity in context.MyEntities
                        where firstQuery.Any(otherEntity => otherEntity.Id == entity.Id)
                        select entity;

    var results = secondQuery.ToList();
    ...
}

Contohnya umum, tetapi menggambarkan bagaimana penautan ke firstQuery menyebabkan secondQuery tidak dapat di-cache. Jika firstQuery belum menjadi kueri yang memerlukan kompilasi ulang, maka secondQuery akan di-cache.

5 Kueri NoTracking

5.1 Menonaktifkan pelacakan perubahan untuk mengurangi overhead manajemen status

Jika Anda berada dalam skenario baca-saja dan ingin menghindari overhead memuat objek ke dalam ObjectStateManager, Anda dapat mengeluarkan kueri "Tanpa Pelacakan".  Pelacakan perubahan dapat dinonaktifkan di tingkat kueri.

Perhatikan bahwa dengan menonaktifkan pelacakan perubahan, Anda secara efektif menonaktifkan cache objek. Saat Anda mengkueri entitas, kami tidak dapat melewati materialisasi dengan menarik hasil kueri yang diwujudkan sebelumnya dari ObjectStateManager. Jika Anda berulang kali mengkueri entitas yang sama pada konteks yang sama, Anda mungkin benar-benar melihat manfaat performa dari mengaktifkan pelacakan perubahan.

Saat mengkueri menggunakan instans ObjectContext, ObjectQuery, dan ObjectSet akan mengingat MergeOption setelah diatur, dan kueri yang terdiri dari instans tersebut akan mewarisi MergeOption kueri induk yang efektif. Saat menggunakan DbContext, pelacakan dapat dinonaktifkan dengan memanggil pengubah AsNoTracking() pada DbSet.

5.1.1 Menonaktifkan pelacakan perubahan untuk kueri saat menggunakan DbContext

Anda dapat mengalihkan mode kueri ke NoTracking dengan menautkan panggilan ke metode AsNoTracking() dalam kueri. Tidak seperti ObjectQuery, kelas DbSet dan DbQuery di API DbContext tidak memiliki properti yang dapat diubah untuk MergeOption.

    var productsForCategory = from p in context.Products.AsNoTracking()
                                where p.Category.CategoryName == selectedCategory
                                select p;


5.1.2 Menonaktifkan pelacakan perubahan di tingkat kueri menggunakan ObjectContext

    var productsForCategory = from p in context.Products
                                where p.Category.CategoryName == selectedCategory
                                select p;

    ((ObjectQuery)productsForCategory).MergeOption = MergeOption.NoTracking;

5.1.3 Menonaktifkan pelacakan perubahan untuk seluruh entitas yang ditetapkan menggunakan ObjectContext

    context.Products.MergeOption = MergeOption.NoTracking;

    var productsForCategory = from p in context.Products
                                where p.Category.CategoryName == selectedCategory
                                select p;

5.2 Metrik Pengujian yang menunjukkan manfaat performa kueri NoTracking

Dalam pengujian ini kita melihat biaya mengisi ObjectStateManager dengan membandingkan kueri Pelacakan dengan NoTracking untuk model Navision. Lihat lampiran untuk deskripsi model Navision dan jenis kueri yang dijalankan. Dalam pengujian ini, kami melakukan iterasi melalui daftar kueri dan menjalankan masing-masing sekali. Kami menjalankan dua variasi pengujian, sekali dengan kueri NoTracking dan sekali dengan opsi penggabungan default "AppendOnly". Kami menjalankan setiap variasi 3 kali dan mengambil nilai rata-rata dari eksekusi. Di antara pengujian, kami menghapus cache kueri di SQL Server dan menyusutkan tempdb dengan menjalankan perintah berikut:

  1. DBCC DROPCLEANBUFFERS
  2. DBCC FREEPROCCACHE
  3. DBCC SHRINKDATABASE (tempdb, 0)

Hasil Pengujian, median lebih dari 3 eksekusi:

TIDAK ADA PELACAKAN – SET KERJA TIDAK ADA PELACAKAN – WAKTU TAMBAHKAN SAJA – SET KERJA TAMBAHKAN SAJA – WAKTU
Kerangka Kerja Entitas 5 460361728 1163536 ms 596545536 1273042 ms
Kerangka Kerja Entitas 6 647127040 190228 ms 832798720 195521 ms

Entity Framework 5 akan memiliki jejak memori yang lebih kecil di akhir eksekusi daripada Entity Framework 6. Memori tambahan yang digunakan oleh Entity Framework 6 adalah hasil dari struktur memori dan kode tambahan yang memungkinkan fitur baru dan performa yang lebih baik.

Ada juga perbedaan yang jelas dalam jejak memori saat menggunakan ObjectStateManager. Entity Framework 5 meningkatkan jejaknya sebesar 30% saat melacak semua entitas yang kami materialisasi dari database. Entity Framework 6 meningkatkan jejaknya sebesar 28% saat melakukannya.

Dalam hal waktu, Entity Framework 6 mengungguli Entity Framework 5 dalam pengujian ini dengan margin besar. Entity Framework 6 menyelesaikan pengujian sekitar 16% dari waktu yang dikonsumsi oleh Entity Framework 5. Selain itu, Entity Framework 5 membutuhkan waktu 9% lebih lama untuk diselesaikan ketika ObjectStateManager sedang digunakan. Sebagai perbandingan, Entity Framework 6 menggunakan 3% lebih banyak waktu saat menggunakan ObjectStateManager.

6 Opsi Eksekusi Kueri

Entity Framework menawarkan beberapa cara berbeda untuk mengkueri. Kita akan melihat opsi berikut, membandingkan pro dan kontra masing-masing, dan memeriksa karakteristik performanya:

  • LINQ ke Entitas.
  • Tidak ada Pelacakan LINQ ke Entitas.
  • SQL Entitas melalui ObjectQuery.
  • SQL Entitas melalui EntityCommand.
  • ExecuteStoreQuery.
  • SqlQuery.
  • CompiledQuery.

6.1 LINQ ke kueri Entitas

var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");

Pro

  • Cocok untuk operasi CUD.
  • Objek yang sepenuhnya terwujud.
  • Paling sederhana untuk menulis dengan sintaksis yang disertakan dalam bahasa pemrograman.
  • Performa yang bagus.

Kontra

  • Pembatasan teknis tertentu, seperti:
    • Pola yang menggunakan DefaultIfEmpty untuk kueri OUTER JOIN menghasilkan kueri yang lebih kompleks daripada pernyataan OUTER JOIN sederhana di Entity SQL.
    • Anda masih tidak dapat menggunakan LIKE dengan pencocokan pola umum.

6.2 Tidak Ada LINQ Pelacakan ke kueri Entitas

Saat konteks memperoleh ObjectContext:

context.Products.MergeOption = MergeOption.NoTracking;
var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");

Saat konteks memperoleh DbContext:

var q = context.Products.AsNoTracking()
                        .Where(p => p.Category.CategoryName == "Beverages");

Pro

  • Peningkatan performa atas kueri LINQ reguler.
  • Objek yang sepenuhnya terwujud.
  • Paling sederhana untuk menulis dengan sintaksis yang disertakan dalam bahasa pemrograman.

Kontra

  • Tidak cocok untuk operasi CUD.
  • Pembatasan teknis tertentu, seperti:
    • Pola yang menggunakan DefaultIfEmpty untuk kueri OUTER JOIN menghasilkan kueri yang lebih kompleks daripada pernyataan OUTER JOIN sederhana di Entity SQL.
    • Anda masih tidak dapat menggunakan LIKE dengan pencocokan pola umum.

Perhatikan bahwa kueri bahwa properti skalar proyek tidak dilacak meskipun NoTracking tidak ditentukan. Contohnya:

var q = context.Products.Where(p => p.Category.CategoryName == "Beverages").Select(p => new { p.ProductName });

Kueri khusus ini tidak secara eksplisit menentukan menjadi NoTracking, tetapi karena tidak mewujudkan jenis yang diketahui oleh manajer status objek maka hasil materialisasi tidak dilacak.

6.3 SQL Entitas melalui ObjectQuery

ObjectQuery<Product> products = context.Products.Where("it.Category.CategoryName = 'Beverages'");

Pro

  • Cocok untuk operasi CUD.
  • Objek yang sepenuhnya terwujud.
  • Mendukung penembolokan rencana kueri.

Kontra

  • Melibatkan string kueri tekstual yang lebih rentan terhadap kesalahan pengguna daripada konstruksi kueri yang disertakan dalam bahasa.

6.4 SQL Entitas melalui Perintah Entitas

EntityCommand cmd = eConn.CreateCommand();
cmd.CommandText = "Select p From NorthwindEntities.Products As p Where p.Category.CategoryName = 'Beverages'";

using (EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
    while (reader.Read())
    {
        // manually 'materialize' the product
    }
}

Pro

  • Mendukung penembolokan rencana kueri di .NET 4.0 (penembolokan paket didukung oleh semua jenis kueri lainnya di .NET 4.5).

Kontra

  • Melibatkan string kueri tekstual yang lebih rentan terhadap kesalahan pengguna daripada konstruksi kueri yang disertakan dalam bahasa.
  • Tidak cocok untuk operasi CUD.
  • Hasil tidak secara otomatis terwujud, dan harus dibaca dari pembaca data.

6.5 SqlQuery dan ExecuteStoreQuery

SqlQuery di Database:

// use this to obtain entities and not track them
var q1 = context.Database.SqlQuery<Product>("select * from products");

SqlQuery di DbSet:

// use this to obtain entities and have them tracked
var q2 = context.Products.SqlQuery("select * from products");

ExecuteStoreQuery:

var beverages = context.ExecuteStoreQuery<Product>(
@"     SELECT        P.ProductID, P.ProductName, P.SupplierID, P.CategoryID, P.QuantityPerUnit, P.UnitPrice, P.UnitsInStock, P.UnitsOnOrder, P.ReorderLevel, P.Discontinued, P.DiscontinuedDate
       FROM            Products AS P INNER JOIN Categories AS C ON P.CategoryID = C.CategoryID
       WHERE        (C.CategoryName = 'Beverages')"
);

Pro

  • Umumnya performa tercepat karena kompiler rencana dilewati.
  • Objek yang sepenuhnya terwujud.
  • Cocok untuk operasi CUD saat digunakan dari DbSet.

Kontra

  • Kueri rawan tekstual dan kesalahan.
  • Kueri terkait dengan backend tertentu dengan menggunakan semantik penyimpanan alih-alih semantik konseptual.
  • Saat pewarisan ada, kueri buatan tangan perlu memperhitungkan kondisi pemetaan untuk jenis yang diminta.

6.6 CompiledQuery

private static readonly Func<NorthwindEntities, string, IQueryable<Product>> productsForCategoryCQ = CompiledQuery.Compile(
    (NorthwindEntities context, string categoryName) =>
        context.Products.Where(p => p.Category.CategoryName == categoryName)
        );
…
var q = context.InvokeProductsForCategoryCQ("Beverages");

Pro

  • Memberikan peningkatan performa hingga 7% dibandingkan kueri LINQ reguler.
  • Objek yang sepenuhnya terwujud.
  • Cocok untuk operasi CUD.

Kontra

  • Peningkatan kompleksitas dan overhead pemrograman.
  • Peningkatan performa hilang saat menyusun di atas kueri yang dikompilasi.
  • Beberapa kueri LINQ tidak dapat ditulis sebagai CompiledQuery - misalnya, proyeksi jenis anonim.

6.7 Perbandingan Performa opsi kueri yang berbeda

Tolok ukur mikro sederhana di mana pembuatan konteks tidak diberi waktu untuk pengujian. Kami mengukur kueri 5000 kali untuk sekumpulan entitas yang tidak di-cache di lingkungan terkontrol. Angka-angka ini harus diambil dengan peringatan: mereka tidak mencerminkan angka aktual yang dihasilkan oleh aplikasi, tetapi sebaliknya mereka adalah pengukuran yang sangat akurat tentang berapa banyak perbedaan performa yang ada ketika opsi kueri yang berbeda dibandingkan apples-to-apples, tidak termasuk biaya pembuatan konteks baru.

EF Uji Time (md) Memori
EF5 ObjectContext ESQL 2414 38801408
EF5 Kueri Linq ObjectContext 2692 38277120
EF5 DbContext Linq Query No Tracking 2818 41840640
EF5 Kueri DbContext Linq 2930 41771008
EF5 ObjectContext Linq Query No Tracking 3013 38412288
EF6 ObjectContext ESQL 2059 46039040
EF6 Kueri Linq ObjectContext 3074 45248512
EF6 DbContext Linq Query No Tracking 3125 47575040
EF6 Kueri DbContext Linq 3420 47652864
EF6 ObjectContext Linq Query No Tracking 3593 45260800

EF5 micro benchmarks, 5000 warm iterations

EF6 micro benchmarks, 5000 warm iterations

Tolok ukur mikro sangat sensitif terhadap perubahan kecil dalam kode. Dalam hal ini, perbedaan antara biaya Kerangka Kerja Entitas 5 dan Kerangka Kerja Entitas 6 disebabkan oleh penambahan intersepsi dan peningkatan transaksi. Namun, angka-angka microbenchmarks ini adalah visi yang diperkuat menjadi fragmen yang sangat kecil dari apa yang dilakukan Entity Framework. Skenario dunia nyata kueri hangat seharusnya tidak melihat regresi performa saat meningkatkan dari Kerangka Kerja Entitas 5 ke Kerangka Kerja Entitas 6.

Untuk membandingkan performa dunia nyata dari berbagai opsi kueri, kami membuat 5 variasi pengujian terpisah di mana kami menggunakan opsi kueri yang berbeda untuk memilih semua produk yang nama kategorinya adalah "Minuman". Setiap iterasi mencakup biaya pembuatan konteks, dan biaya materialisasi semua entitas yang dikembalikan. 10 iterasi dijalankan tanpa waktu sebelum mengambil jumlah 1000 perulangan berwaktu. Hasil yang ditampilkan adalah eksekusi median yang diambil dari 5 eksekusi setiap pengujian. Untuk informasi selengkapnya, lihat Lampiran B yang menyertakan kode untuk pengujian.

EF Uji Time (md) Memori
EF5 Perintah Entitas ObjectContext 621 39350272
EF5 Kueri Sql DbContext di Database 825 37519360
EF5 Kueri Penyimpanan ObjectContext 878 39460864
EF5 ObjectContext Linq Query No Tracking 969 38293504
EF5 ObjectContext Entity Sql menggunakan Object Query 1089 38981632
EF5 Kueri Yang Dikompilasi ObjectContext 1099 38682624
EF5 Kueri Linq ObjectContext 1152 38178816
EF5 DbContext Linq Query No Tracking 1208 41803776
EF5 DbContext Sql Query di DbSet 1414 37982208
EF5 Kueri DbContext Linq 1574 41738240
EF6 Perintah Entitas ObjectContext 480 47247360
EF6 Kueri Penyimpanan ObjectContext 493 46739456
EF6 Kueri Sql DbContext di Database 614 41607168
EF6 ObjectContext Linq Query No Tracking 684 46333952
EF6 ObjectContext Entity Sql menggunakan Object Query 767 48865280
EF6 Kueri Yang Dikompilasi ObjectContext 788 48467968
EF6 DbContext Linq Query No Tracking 878 47554560
EF6 Kueri Linq ObjectContext 953 47632384
EF6 DbContext Sql Query di DbSet 1023 41992192
EF6 Kueri DbContext Linq 1290 47529984

EF5 warm query 1000 iterations

EF6 warm query 1000 iterations

Catatan

Untuk kelengkapan, kami menyertakan variasi tempat kami menjalankan kueri Entity SQL pada EntityCommand. Namun, karena hasil tidak terwujud untuk kueri tersebut, perbandingan belum tentu apel ke apel. Pengujian ini mencakup perkiraan yang dekat untuk mewujudkan untuk mencoba membuat perbandingan lebih adil.

Dalam kasus end-to-end ini, Entity Framework 6 mengungguli Entity Framework 5 karena peningkatan performa yang dilakukan pada beberapa bagian tumpukan, termasuk inisialisasi DbContext yang jauh lebih ringan dan pencarian MetadataCollection<T> yang lebih cepat.

7 Pertimbangan performa waktu desain

7.1 Strategi Warisan

Pertimbangan performa lain saat menggunakan Kerangka Kerja Entitas adalah strategi pewarisan yang Anda gunakan. Entity Framework mendukung 3 jenis pewarisan dasar dan kombinasinya:

  • Tabel per Hierarki (TPH) – di mana setiap pewarisan mengatur peta ke tabel dengan kolom diskriminator untuk menunjukkan jenis tertentu dalam hierarki mana yang diwakili dalam baris.
  • Tabel per Jenis (TPT) – di mana setiap jenis memiliki tabelnya sendiri dalam database; tabel anak hanya menentukan kolom yang tidak dimuat tabel induk.
  • Tabel per Kelas (TPC) – di mana setiap jenis memiliki tabel lengkapnya sendiri dalam database; tabel anak menentukan semua bidangnya, termasuk yang ditentukan dalam jenis induk.

Jika model Anda menggunakan pewarisan TPT, kueri yang dihasilkan akan lebih kompleks daripada yang dihasilkan dengan strategi warisan lainnya, yang dapat mengakibatkan waktu eksekusi yang lebih lama di penyimpanan.  Umumnya akan memakan waktu lebih lama untuk menghasilkan kueri melalui model TPT, dan untuk mewujudkan objek yang dihasilkan.

Lihat posting blog MSDN "Pertimbangan Performa saat menggunakan Warisan TPT (Tabel per Jenis) dalam Kerangka Kerja Entitas" : <https://learn.microsoft.com/archive/blogs/adonet/performance-considerations-when-using-tpt-table-per-type-inheritance-in-the-entity-framework>.

7.1.1 Menghindari TPT di Aplikasi Model Pertama atau Kode Pertama

Saat Anda membuat model melalui database yang sudah ada yang memiliki skema TPT, Anda tidak memiliki banyak opsi. Tetapi saat membuat aplikasi menggunakan Model First atau Code First, Anda harus menghindari warisan TPT untuk masalah performa.

Saat Anda menggunakan Model Pertama dalam Wizard Perancang Entitas, Anda akan mendapatkan TPT untuk warisan apa pun dalam model Anda. Jika Anda ingin beralih ke strategi pewarisan TPH dengan Model First, Anda dapat menggunakan "Entity Designer Database Generation Power Pack" yang tersedia dari Visual Studio Gallery ( <http://visualstudiogallery.msdn.microsoft.com/df3541c3-d833-4b65-b942-989e7ec74c87/>).

Saat menggunakan Code First untuk mengonfigurasi pemetaan model dengan pewarisan, EF akan menggunakan TPH secara default, oleh karena itu semua entitas dalam hierarki pewarisan akan dipetakan ke tabel yang sama. Lihat bagian "Pemetaan dengan API Fasih" dari artikel "Code First in Entity Framework4.1" di Majalah MSDN ( http://msdn.microsoft.com/magazine/hh126815.aspx) untuk detail selengkapnya.

7.2 Peningkatan dari EF4 untuk meningkatkan waktu pembuatan model

Peningkatan khusus SQL Server pada algoritma yang menghasilkan lapisan penyimpanan (SSDL) model tersedia di Entity Framework 5 dan 6, dan sebagai pembaruan ke Entity Framework 4 saat Visual Studio 2010 SP1 diinstal. Hasil pengujian berikut menunjukkan peningkatan saat menghasilkan model yang sangat besar, dalam hal ini model Navision. Lihat Lampiran C untuk detail selengkapnya tentang hal itu.

Model ini berisi 1005 set entitas dan 4227 set asosiasi.

Konfigurasi Perincian waktu yang dikonsumsi
Visual Studio 2010, Entity Framework 4 Generasi SSDL: 2 jam 27 menit
Generasi Pemetaan: 1 detik
Generasi CSDL: 1 detik
Pembuatan ObjectLayer: 1 detik
Lihat Generasi: 2 jam 14 menit
Visual Studio 2010 SP1, Entity Framework 4 Generasi SSDL: 1 detik
Generasi Pemetaan: 1 detik
Generasi CSDL: 1 detik
Pembuatan ObjectLayer: 1 detik
Lihat Generasi: 1 jam 53 menit
Visual Studio 2013, Entity Framework 5 Generasi SSDL: 1 detik
Generasi Pemetaan: 1 detik
Generasi CSDL: 1 detik
Pembuatan ObjectLayer: 1 detik
Lihat Generasi: 65 menit
Visual Studio 2013, Entity Framework 6 Generasi SSDL: 1 detik
Generasi Pemetaan: 1 detik
Generasi CSDL: 1 detik
Pembuatan ObjectLayer: 1 detik
Lihat Generasi: 28 detik.

Perlu dicatat bahwa ketika menghasilkan SSDL, beban hampir seluruhnya dihabiskan di SQL Server, sementara mesin pengembangan klien menunggu hasil kembali dari server. DBA harus sangat menghargai peningkatan ini. Perlu juga dicatat bahwa pada dasarnya seluruh biaya pembuatan model terjadi di View Generation sekarang.

7.3 Memisahkan Model Besar dengan Database Pertama dan Model Pertama

Ketika ukuran model meningkat, permukaan perancang menjadi berantakan dan sulit digunakan. Kami biasanya mempertimbangkan model dengan lebih dari 300 entitas terlalu besar untuk menggunakan perancang secara efektif. Posting blog berikut menjelaskan beberapa opsi untuk memisahkan model besar: <https://learn.microsoft.com/archive/blogs/adonet/working-with-large-models-in-entity-framework-part-2>.

Postingan ditulis untuk versi pertama Kerangka Kerja Entitas, tetapi langkah-langkahnya masih berlaku.

7.4 Pertimbangan performa dengan Kontrol Sumber Data Entitas

Kami telah melihat kasus dalam performa multi-utas dan pengujian stres di mana performa aplikasi web menggunakan EntityDataSource Control memburuk secara signifikan. Penyebab yang mendasarinya adalah bahwa EntityDataSource berulang kali memanggil MetadataWorkspace.LoadFromAssembly pada rakitan yang direferensikan oleh aplikasi Web untuk menemukan jenis yang akan digunakan sebagai entitas.

Solusinya adalah mengatur ContextTypeName dari EntityDataSource ke nama jenis kelas ObjectContext turunan Anda. Ini menonaktifkan mekanisme yang memindai semua rakitan yang dirujuk untuk jenis entitas.

Mengatur bidang ContextTypeName juga mencegah masalah fungsional di mana EntityDataSource di .NET 4.0 melempar ReflectionTypeLoadException saat tidak dapat memuat jenis dari rakitan melalui pantulan. Masalah ini telah diperbaiki di .NET 4.5.

7.5 entitas POCO dan proksi pelacakan perubahan

Entity Framework memungkinkan Anda menggunakan kelas data kustom bersama dengan model data Anda tanpa melakukan modifikasi apa pun pada kelas data itu sendiri. Ini berarti Anda dapat menggunakan objek CLR (POCO) "plain-old", seperti objek domain yang ada, dengan model data Anda. Kelas data POCO ini (juga dikenal sebagai objek yang tidak tahu persistensi), yang dipetakan ke entitas yang ditentukan dalam model data, mendukung sebagian besar kueri, menyisipkan, memperbarui, dan menghapus perilaku yang sama sebagai jenis entitas yang dihasilkan oleh alat Model Data Entitas.

Entity Framework juga dapat membuat kelas proksi yang berasal dari jenis POCO Anda, yang digunakan saat Anda ingin mengaktifkan fitur seperti pemuatan malas dan pelacakan perubahan otomatis pada entitas POCO. Kelas POCO Anda harus memenuhi persyaratan tertentu untuk memungkinkan Kerangka Kerja Entitas menggunakan proksi, seperti yang dijelaskan di sini: http://msdn.microsoft.com/library/dd468057.aspx.

Kemungkinan melacak proksi akan memberi tahu manajer status objek setiap kali properti entitas Anda berubah nilainya, sehingga Entity Framework mengetahui status aktual entitas Anda sepanjang waktu. Ini dilakukan dengan menambahkan peristiwa pemberitahuan ke isi metode setter properti Anda, dan meminta manajer status objek memproses peristiwa tersebut. Perhatikan bahwa membuat entitas proksi biasanya akan lebih mahal daripada membuat entitas POCO non-proksi karena serangkaian peristiwa tambahan yang dibuat oleh Entity Framework.

Ketika entitas POCO tidak memiliki proksi pelacakan perubahan, perubahan ditemukan dengan membandingkan konten entitas Anda dengan salinan status tersimpan sebelumnya. Perbandingan mendalam ini akan menjadi proses yang panjang ketika Anda memiliki banyak entitas dalam konteks Anda, atau ketika entitas Anda memiliki sejumlah besar properti, bahkan jika tidak ada yang berubah sejak perbandingan terakhir terjadi.

Singkatnya: Anda akan membayar hit performa saat membuat proksi pelacakan perubahan, tetapi pelacakan perubahan akan membantu Anda mempercepat proses deteksi perubahan ketika entitas Anda memiliki banyak properti atau ketika Anda memiliki banyak entitas dalam model Anda. Untuk entitas dengan sejumlah kecil properti di mana jumlah entitas tidak tumbuh terlalu banyak, memiliki proksi pelacakan perubahan mungkin tidak banyak manfaatnya.

8.1 Pemuatan Malas vs. Pemuatan Bersemangat

Entity Framework menawarkan beberapa cara berbeda untuk memuat entitas yang terkait dengan entitas target Anda. Misalnya, saat Anda mengkueri Produk, ada berbagai cara agar Pesanan terkait akan dimuat ke dalam Object State Manager. Dari sudut pandang performa, pertanyaan terbesar yang perlu dipertimbangkan saat memuat entitas terkait adalah apakah akan menggunakan Lazy Loading atau Eager Loading.

Saat menggunakan Eager Loading, entitas terkait dimuat bersama dengan kumpulan entitas target Anda. Anda menggunakan pernyataan Sertakan dalam kueri Anda untuk menunjukkan entitas terkait mana yang ingin Anda bawa.

Saat menggunakan Pemuatan Malas, kueri awal Anda hanya membawa kumpulan entitas target. Tetapi setiap kali Anda mengakses properti navigasi, kueri lain dikeluarkan terhadap penyimpanan untuk memuat entitas terkait.

Setelah entitas dimuat, kueri lebih lanjut untuk entitas akan memuatnya langsung dari Object State Manager, baik Anda menggunakan pemuatan malas atau pemuatan yang bersemangat.

8.2 Cara memilih antara Pemuatan Malas dan Pemuatan Bersemangat

Yang penting adalah Anda memahami perbedaan antara Lazy Loading dan Eager Loading sehingga Anda dapat membuat pilihan yang benar untuk aplikasi Anda. Ini akan membantu Anda mengevaluasi tradeoff antara beberapa permintaan terhadap database versus satu permintaan yang mungkin berisi payload besar. Mungkin tepat untuk menggunakan pemuatan bersemangat di beberapa bagian aplikasi Anda dan pemuatan malas di bagian lain.

Sebagai contoh apa yang terjadi di bawah tenda, misalkan Anda ingin meminta pelanggan yang tinggal di Inggris dan jumlah pesanan mereka.

Menggunakan Eager Loading

using (NorthwindEntities context = new NorthwindEntities())
{
    var ukCustomers = context.Customers.Include(c => c.Orders).Where(c => c.Address.Country == "UK");
    var chosenCustomer = AskUserToPickCustomer(ukCustomers);
    Console.WriteLine("Customer Id: {0} has {1} orders", customer.CustomerID, customer.Orders.Count);
}

Menggunakan Pemuatan Malas

using (NorthwindEntities context = new NorthwindEntities())
{
    context.ContextOptions.LazyLoadingEnabled = true;

    //Notice that the Include method call is missing in the query
    var ukCustomers = context.Customers.Where(c => c.Address.Country == "UK");

    var chosenCustomer = AskUserToPickCustomer(ukCustomers);
    Console.WriteLine("Customer Id: {0} has {1} orders", customer.CustomerID, customer.Orders.Count);
}

Saat menggunakan pemuatan bersemangat, Anda akan mengeluarkan satu kueri yang mengembalikan semua pelanggan dan semua pesanan. Perintah penyimpanan terlihat seperti:

SELECT
[Project1].[C1] AS [C1],
[Project1].[CustomerID] AS [CustomerID],
[Project1].[CompanyName] AS [CompanyName],
[Project1].[ContactName] AS [ContactName],
[Project1].[ContactTitle] AS [ContactTitle],
[Project1].[Address] AS [Address],
[Project1].[City] AS [City],
[Project1].[Region] AS [Region],
[Project1].[PostalCode] AS [PostalCode],
[Project1].[Country] AS [Country],
[Project1].[Phone] AS [Phone],
[Project1].[Fax] AS [Fax],
[Project1].[C2] AS [C2],
[Project1].[OrderID] AS [OrderID],
[Project1].[CustomerID1] AS [CustomerID1],
[Project1].[EmployeeID] AS [EmployeeID],
[Project1].[OrderDate] AS [OrderDate],
[Project1].[RequiredDate] AS [RequiredDate],
[Project1].[ShippedDate] AS [ShippedDate],
[Project1].[ShipVia] AS [ShipVia],
[Project1].[Freight] AS [Freight],
[Project1].[ShipName] AS [ShipName],
[Project1].[ShipAddress] AS [ShipAddress],
[Project1].[ShipCity] AS [ShipCity],
[Project1].[ShipRegion] AS [ShipRegion],
[Project1].[ShipPostalCode] AS [ShipPostalCode],
[Project1].[ShipCountry] AS [ShipCountry]
FROM ( SELECT
      [Extent1].[CustomerID] AS [CustomerID],
       [Extent1].[CompanyName] AS [CompanyName],
       [Extent1].[ContactName] AS [ContactName],
       [Extent1].[ContactTitle] AS [ContactTitle],
       [Extent1].[Address] AS [Address],
       [Extent1].[City] AS [City],
       [Extent1].[Region] AS [Region],
       [Extent1].[PostalCode] AS [PostalCode],
       [Extent1].[Country] AS [Country],
       [Extent1].[Phone] AS [Phone],
       [Extent1].[Fax] AS [Fax],
      1 AS [C1],
       [Extent2].[OrderID] AS [OrderID],
       [Extent2].[CustomerID] AS [CustomerID1],
       [Extent2].[EmployeeID] AS [EmployeeID],
       [Extent2].[OrderDate] AS [OrderDate],
       [Extent2].[RequiredDate] AS [RequiredDate],
       [Extent2].[ShippedDate] AS [ShippedDate],
       [Extent2].[ShipVia] AS [ShipVia],
       [Extent2].[Freight] AS [Freight],
       [Extent2].[ShipName] AS [ShipName],
       [Extent2].[ShipAddress] AS [ShipAddress],
       [Extent2].[ShipCity] AS [ShipCity],
       [Extent2].[ShipRegion] AS [ShipRegion],
       [Extent2].[ShipPostalCode] AS [ShipPostalCode],
       [Extent2].[ShipCountry] AS [ShipCountry],
      CASE WHEN ([Extent2].[OrderID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
      FROM  [dbo].[Customers] AS [Extent1]
      LEFT OUTER JOIN [dbo].[Orders] AS [Extent2] ON [Extent1].[CustomerID] = [Extent2].[CustomerID]
      WHERE N'UK' = [Extent1].[Country]
)  AS [Project1]
ORDER BY [Project1].[CustomerID] ASC, [Project1].[C2] ASC

Saat menggunakan pemuatan malas, Anda akan mengeluarkan kueri berikut pada awalnya:

SELECT
[Extent1].[CustomerID] AS [CustomerID],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[ContactName] AS [ContactName],
[Extent1].[ContactTitle] AS [ContactTitle],
[Extent1].[Address] AS [Address],
[Extent1].[City] AS [City],
[Extent1].[Region] AS [Region],
[Extent1].[PostalCode] AS [PostalCode],
[Extent1].[Country] AS [Country],
[Extent1].[Phone] AS [Phone],
[Extent1].[Fax] AS [Fax]
FROM [dbo].[Customers] AS [Extent1]
WHERE N'UK' = [Extent1].[Country]

Dan setiap kali Anda mengakses properti navigasi Pesanan dari kueri lain pelanggan seperti berikut ini dikeluarkan terhadap penyimpanan:

exec sp_executesql N'SELECT
[Extent1].[OrderID] AS [OrderID],
[Extent1].[CustomerID] AS [CustomerID],
[Extent1].[EmployeeID] AS [EmployeeID],
[Extent1].[OrderDate] AS [OrderDate],
[Extent1].[RequiredDate] AS [RequiredDate],
[Extent1].[ShippedDate] AS [ShippedDate],
[Extent1].[ShipVia] AS [ShipVia],
[Extent1].[Freight] AS [Freight],
[Extent1].[ShipName] AS [ShipName],
[Extent1].[ShipAddress] AS [ShipAddress],
[Extent1].[ShipCity] AS [ShipCity],
[Extent1].[ShipRegion] AS [ShipRegion],
[Extent1].[ShipPostalCode] AS [ShipPostalCode],
[Extent1].[ShipCountry] AS [ShipCountry]
FROM [dbo].[Orders] AS [Extent1]
WHERE [Extent1].[CustomerID] = @EntityKeyValue1',N'@EntityKeyValue1 nchar(5)',@EntityKeyValue1=N'AROUT'

Untuk informasi selengkapnya, lihat Memuat Objek Terkait.

8.2.1 Lazy Loading versus Eager Loading cheat sheet

Tidak ada hal seperti satu ukuran-cocok-semua untuk memilih pemuatan bersemangat versus pemuatan malas. Cobalah terlebih dahulu untuk memahami perbedaan antara kedua strategi sehingga Anda dapat melakukan keputusan yang tepat; juga, pertimbangkan apakah kode Anda sesuai dengan salah satu skenario berikut:

Skenario Saran Kami
Apakah Anda perlu mengakses banyak properti navigasi dari entitas yang diambil? Tidak - Kedua opsi mungkin akan dilakukan. Namun, jika payload yang dibawa kueri Anda tidak terlalu besar, Anda mungkin mengalami manfaat performa dengan menggunakan pemuatan Eager karena akan membutuhkan lebih sedikit perjalanan pulang pergi jaringan untuk mewujudkan objek Anda.

Ya - Jika Anda perlu mengakses banyak properti navigasi dari entitas, Anda akan melakukannya dengan menggunakan beberapa pernyataan sertakan dalam kueri Anda dengan pemuatan Eager. Semakin banyak entitas yang Anda sertakan, semakin besar payload yang akan dikembalikan kueri Anda. Setelah Anda menyertakan tiga entitas atau lebih ke dalam kueri Anda, pertimbangkan untuk beralih ke Pemuatan Malas.
Apakah Anda tahu persis data apa yang akan diperlukan pada waktu proses? Tidak - Pemuatan malas akan lebih baik untuk Anda. Jika tidak, Anda mungkin akhirnya mengkueri data yang tidak akan Anda butuhkan.

Ya - Pemuatan bersemangat mungkin merupakan best bet Anda; ini akan membantu memuat seluruh set lebih cepat. Jika kueri Anda memerlukan pengambilan data dalam jumlah yang sangat besar, dan ini menjadi terlalu lambat, maka coba Pemuatan malas sebagai gantinya.
Apakah kode Anda berjalan jauh dari database Anda? (peningkatan latensi jaringan) Tidak - Ketika latensi jaringan bukan masalah, menggunakan pemuatan Malas dapat menyederhanakan kode Anda. Ingatlah bahwa topologi aplikasi Anda dapat berubah, jadi jangan mengambil kedekatan database untuk diberikan.

Ya - Ketika jaringan bermasalah, hanya Anda yang dapat memutuskan apa yang lebih cocok untuk skenario Anda. Biasanya pemuatan Eager akan lebih baik karena membutuhkan lebih sedikit perjalanan pulang pergi.

8.2.2 Masalah performa dengan beberapa Termasuk

Ketika kami mendengar pertanyaan performa yang melibatkan masalah waktu respons server, sumber masalah sering dikueri dengan beberapa pernyataan Sertakan. Meskipun termasuk entitas terkait dalam kueri sangat kuat, penting untuk memahami apa yang terjadi di bawah sampul.

Dibutuhkan waktu yang relatif lama untuk kueri dengan beberapa pernyataan Sertakan di dalamnya untuk melalui kompilator rencana internal kami untuk menghasilkan perintah penyimpanan. Sebagian besar waktu ini dihabiskan untuk mencoba mengoptimalkan kueri yang dihasilkan. Perintah penyimpanan yang dihasilkan akan berisi Gabungan Luar atau Gabungan untuk setiap Sertakan, tergantung pada pemetaan Anda. Kueri seperti ini akan membawa grafik terhubung besar dari database Anda dalam satu payload, yang akan mengasingkan masalah bandwidth apa pun, terutama ketika ada banyak redundansi dalam payload (misalnya, ketika beberapa tingkat Serta digunakan untuk melintasi asosiasi ke arah satu-ke-banyak).

Anda dapat memeriksa kasus di mana kueri Anda mengembalikan payload yang terlalu besar dengan mengakses TSQL yang mendasari untuk kueri dengan menggunakan ToTraceString dan menjalankan perintah penyimpanan di SQL Server Management Studio untuk melihat ukuran payload. Dalam kasus seperti itu, Anda dapat mencoba mengurangi jumlah pernyataan Sertakan dalam kueri Anda untuk hanya membawa data yang Anda butuhkan. Atau Anda mungkin dapat memecah kueri Anda menjadi urutan subkueri yang lebih kecil, misalnya:

Sebelum memutuskan kueri:

using (NorthwindEntities context = new NorthwindEntities())
{
    var customers = from c in context.Customers.Include(c => c.Orders)
                    where c.LastName.StartsWith(lastNameParameter)
                    select c;

    foreach (Customer customer in customers)
    {
        ...
    }
}

Setelah melanggar kueri:

using (NorthwindEntities context = new NorthwindEntities())
{
    var orders = from o in context.Orders
                 where o.Customer.LastName.StartsWith(lastNameParameter)
                 select o;

    orders.Load();

    var customers = from c in context.Customers
                    where c.LastName.StartsWith(lastNameParameter)
                    select c;

    foreach (Customer customer in customers)
    {
        ...
    }
}

Ini hanya akan berfungsi pada kueri terlacak, karena kami memanfaatkan kemampuan yang dimiliki konteks untuk melakukan resolusi identitas dan perbaikan asosiasi secara otomatis.

Seperti halnya pemuatan malas, tradeoff akan menjadi lebih banyak kueri untuk payload yang lebih kecil. Anda juga dapat menggunakan proyeksi properti individual untuk secara eksplisit hanya memilih data yang Anda butuhkan dari setiap entitas, tetapi Anda tidak akan memuat entitas dalam kasus ini, dan pembaruan tidak akan didukung.

8.2.3 Solusi untuk mendapatkan pemuatan properti yang malas

Entity Framework saat ini tidak mendukung pemuatan properti skalar atau kompleks yang malas. Namun, dalam kasus di mana Anda memiliki tabel yang menyertakan objek besar seperti BLOB, Anda dapat menggunakan pemisahan tabel untuk memisahkan properti besar menjadi entitas terpisah. Misalnya, Anda memiliki tabel Produk yang menyertakan kolom foto varbinary. Jika Anda tidak sering perlu mengakses properti ini dalam kueri, Anda dapat menggunakan pemisahan tabel untuk hanya membawa bagian entitas yang biasanya Anda butuhkan. Entitas yang mewakili foto produk hanya akan dimuat ketika Anda secara eksplisit membutuhkannya.

Sumber daya yang baik yang menunjukkan cara mengaktifkan pemisahan tabel adalah posting blog "Pemisahan Tabel di Kerangka Kerja Entitas" Gil Fink: <http://blogs.microsoft.co.il/blogs/gilf/archive/2009/10/13/table-splitting-in-entity-framework.aspx>.

9 Pertimbangan lain

Pengumpulan Sampah Server 9.1

Beberapa pengguna mungkin mengalami ketidakcocokan sumber daya yang membatasi paralelisme yang mereka harapkan ketika Pengumpul Sampah tidak dikonfigurasi dengan benar. Setiap kali EF digunakan dalam skenario multithreaded, atau dalam aplikasi apa pun yang menyerupai sistem sisi server, pastikan untuk mengaktifkan Pengumpulan Sampah Server. Ini dilakukan melalui pengaturan sederhana dalam file konfigurasi aplikasi Anda:

<?xmlversion="1.0" encoding="utf-8" ?>
<configuration>
        <runtime>
               <gcServer enabled="true" />
        </runtime>
</configuration>

Ini harus mengurangi ketidakcocokan utas Anda dan meningkatkan throughput Anda hingga 30% dalam skenario jenuh CPU. Secara umum, Anda harus selalu menguji bagaimana aplikasi Anda berperilaku menggunakan Pengumpulan Sampah klasik (yang lebih baik disetel untuk UI dan skenario sisi klien) serta Pengumpulan Sampah Server.

9.2 AutoDetectChanges

Seperti disebutkan sebelumnya, Kerangka Kerja Entitas mungkin menunjukkan masalah performa ketika cache objek memiliki banyak entitas. Operasi tertentu, seperti Tambahkan, Hapus, Temukan, Entri, dan SaveChanges, panggilan pemicu ke DetectChanges yang mungkin menggunakan sejumlah besar CPU berdasarkan seberapa besar cache objek. Alasan untuk ini adalah bahwa cache objek dan manajer status objek mencoba untuk tetap sinkron mungkin pada setiap operasi yang dilakukan ke konteks sehingga data yang dihasilkan dijamin benar di bawah berbagai skenario.

Ini umumnya merupakan praktik yang baik untuk membiarkan deteksi perubahan otomatis Entity Framework diaktifkan selama seluruh masa pakai aplikasi Anda. Jika skenario Anda dipengaruhi secara negatif oleh penggunaan CPU yang tinggi dan profil Anda menunjukkan bahwa pelakunya adalah panggilan ke DetectChanges, pertimbangkan untuk menonaktifkan AutoDetectChanges untuk sementara waktu di bagian sensitif kode Anda:

try
{
    context.Configuration.AutoDetectChangesEnabled = false;
    var product = context.Products.Find(productId);
    ...
}
finally
{
    context.Configuration.AutoDetectChangesEnabled = true;
}

Sebelum menonaktifkan AutoDetectChanges, ada baiknya untuk memahami bahwa ini dapat menyebabkan Kerangka Kerja Entitas kehilangan kemampuannya untuk melacak informasi tertentu tentang perubahan yang terjadi pada entitas. Jika ditangani dengan tidak benar, ini dapat menyebabkan inkonsistensi data pada aplikasi Anda. Untuk informasi selengkapnya tentang menonaktifkan AutoDetectChanges, baca <http://blog.oneunicorn.com/2012/03/12/secrets-of-detectchanges-part-3-switching-off-automatic-detectchanges/>.

9.3 Konteks per permintaan

Konteks Entity Framework dimaksudkan untuk digunakan sebagai instans berumur pendek untuk memberikan pengalaman performa yang paling optimal. Konteks diharapkan berumur pendek dan dibuang, dan dengan demikian telah diimplementasikan menjadi sangat ringan dan menggunakan kembali metadata jika memungkinkan. Dalam skenario web, penting untuk mengingat hal ini dan tidak memiliki konteks selama lebih dari durasi satu permintaan. Demikian pula, dalam skenario non-web, konteks harus dibuang berdasarkan pemahaman Anda tentang berbagai tingkat penembolokan dalam Kerangka Kerja Entitas. Secara umum, seseorang harus menghindari memiliki instans konteks sepanjang masa pakai aplikasi, serta konteks per utas dan konteks statis.

9.4 Semantik null database

Entity Framework secara default akan menghasilkan kode SQL yang memiliki semantik perbandingan null C#. Pertimbangkan contoh kueri berikut:

            int? categoryId = 7;
            int? supplierId = 8;
            decimal? unitPrice = 0;
            short? unitsInStock = 100;
            short? unitsOnOrder = 20;
            short? reorderLevel = null;

            var q = from p incontext.Products
                    where p.Category.CategoryName == "Beverages"
                          || (p.CategoryID == categoryId
                                || p.SupplierID == supplierId
                                || p.UnitPrice == unitPrice
                                || p.UnitsInStock == unitsInStock
                                || p.UnitsOnOrder == unitsOnOrder
                                || p.ReorderLevel == reorderLevel)
                    select p;

            var r = q.ToList();

Dalam contoh ini, kami membandingkan sejumlah variabel nullable dengan properti nullable pada entitas, seperti SupplierID dan UnitPrice. SQL yang dihasilkan untuk kueri ini akan menanyakan apakah nilai parameter sama dengan nilai kolom, atau jika parameter dan nilai kolom null. Ini akan menyembunyikan cara server database menangani null dan akan memberikan pengalaman null C# yang konsisten di berbagai vendor database. Di sisi lain, kode yang dihasilkan sedikit berkonvolusi dan mungkin tidak berkinerja baik ketika jumlah perbandingan di mana pernyataan kueri tumbuh menjadi jumlah besar.

Salah satu cara untuk menangani situasi ini adalah dengan menggunakan semantik null database. Perhatikan bahwa ini mungkin berpotensi berulah secara berbeda dengan semantik null C# karena sekarang Entity Framework akan menghasilkan SQL yang lebih sederhana yang mengekspos cara mesin database menangani nilai null. Semantik null database dapat diaktifkan per konteks dengan satu baris konfigurasi tunggal terhadap konfigurasi konteks:

                context.Configuration.UseDatabaseNullSemantics = true;

Kueri berukuran kecil hingga menengah tidak akan menampilkan peningkatan performa yang terlihat saat menggunakan semantik null database, tetapi perbedaannya akan menjadi lebih terlihat pada kueri dengan sejumlah besar potensi perbandingan null.

Dalam contoh kueri di atas, perbedaan performa kurang dari 2% dalam tolok ukur mikro yang berjalan di lingkungan terkontrol.

9.5 Asinkron

Entity Framework 6 memperkenalkan dukungan operasi asinkron saat berjalan pada .NET 4.5 atau yang lebih baru. Sebagian besar, aplikasi yang memiliki ketidakcocokan terkait IO akan mendapat manfaat paling besar dari penggunaan kueri asinkron dan operasi penyimpanan. Jika aplikasi Anda tidak menderita ketidakcocokan IO, penggunaan asinkron akan, dalam kasus terbaik, berjalan secara sinkron dan mengembalikan hasilnya dalam jumlah waktu yang sama dengan panggilan sinkron, atau dalam kasus terburuk, cukup tunda eksekusi ke tugas asinkron dan tambahkan waktu tambahan untuk penyelesaian skenario Anda.

Untuk informasi tentang cara kerja pemrograman asinkron yang akan membantu Anda memutuskan apakah asinkron akan meningkatkan performa aplikasi Anda, lihat Pemrograman Asinkron dengan Asinkron dan Tunggu. Untuk informasi selengkapnya tentang penggunaan operasi asinkron pada Kerangka Kerja Entitas, lihat Kueri Asinkron dan Simpan.

9.6 NGEN

Entity Framework 6 tidak hadir dalam penginstalan default .NET framework. Dengan demikian, rakitan Kerangka Kerja Entitas tidak NGEN'd secara default yang berarti bahwa semua kode Kerangka Kerja Entitas tunduk pada biaya JIT yang sama dengan rakitan MSIL lainnya. Ini mungkin menurunkan pengalaman F5 saat mengembangkan dan juga startup dingin aplikasi Anda di lingkungan produksi. Untuk mengurangi biaya CPU dan memori JIT'ing, disarankan untuk NGEN gambar Kerangka Kerja Entitas yang sesuai. Untuk informasi selengkapnya tentang cara meningkatkan performa startup Entity Framework 6 dengan NGEN, lihat Meningkatkan Performa Startup dengan NGen.

9.7 Kode Pertama versus EDMX

Alasan Kerangka Kerja Entitas tentang masalah ketidakcocokan impedansi antara pemrograman berorientasi objek dan database relasional dengan memiliki representasi dalam memori dari model konseptual (objek), skema penyimpanan (database) dan pemetaan antara keduanya. Metadata ini disebut Model Data Entitas, atau EDM untuk singkatnya. Dari EDM ini, Entity Framework akan memperoleh tampilan untuk melakukan roundtrip data dari objek dalam memori ke database dan kembali.

Ketika Kerangka Kerja Entitas digunakan dengan file EDMX yang secara resmi menentukan model konseptual, skema penyimpanan, dan pemetaan, maka tahap pemuatan model hanya harus memvalidasi bahwa EDM sudah benar (misalnya, pastikan tidak ada pemetaan yang hilang), lalu buat tampilan, lalu validasi tampilan dan siapkan metadata ini untuk digunakan. Hanya dengan begitu kueri dapat dijalankan atau data baru disimpan ke penyimpanan data.

Pendekatan Code First adalah, pada intinya, generator Model Data Entitas yang canggih. Kerangka Kerja Entitas harus menghasilkan EDM dari kode yang disediakan; itu melakukannya dengan menganalisis kelas yang terlibat dalam model, menerapkan konvensi dan mengonfigurasi model melalui Api Fluent. Setelah EDM dibangun, Kerangka Kerja Entitas pada dasarnya berperilaku dengan cara yang sama seperti file EDMX yang ada dalam proyek. Dengan demikian, membangun model dari Code First menambahkan kompleksitas ekstra yang diterjemahkan ke dalam waktu mulai yang lebih lambat untuk Kerangka Kerja Entitas jika dibandingkan dengan memiliki EDMX. Biaya sepenuhnya tergantung pada ukuran dan kompleksitas model yang sedang dibangun.

Saat memilih untuk menggunakan EDMX versus Kode Pertama, penting untuk mengetahui bahwa fleksibilitas yang diperkenalkan oleh Code First meningkatkan biaya pembangunan model untuk pertama kalinya. Jika aplikasi Anda dapat menahan biaya beban pertama kali ini, biasanya Code First akan menjadi cara yang disukai.

10 Menyelidiki Performa

10.1 Menggunakan Visual Studio Profiler

Jika Anda mengalami masalah performa dengan Kerangka Kerja Entitas, Anda dapat menggunakan profiler seperti yang terpasang di Visual Studio untuk melihat di mana aplikasi Anda menghabiskan waktunya. Ini adalah alat yang kami gunakan untuk menghasilkan bagan pai dalam posting blog "Menjelajahi Performa Kerangka Kerja Entitas ADO.NET - Bagian 1" ( <https://learn.microsoft.com/archive/blogs/adonet/exploring-the-performance-of-the-ado-net-entity-framework-part-1>) yang menunjukkan di mana Kerangka Kerja Entitas menghabiskan waktunya selama kueri dingin dan hangat.

Posting blog "Pembuatan Profiling Entity Framework menggunakan Visual Studio 2010 Profiler" yang ditulis oleh Tim Penasihat Pelanggan Data dan Pemodelan menunjukkan contoh dunia nyata tentang bagaimana mereka menggunakan profiler untuk menyelidiki masalah performa.  <https://learn.microsoft.com/archive/blogs/dmcat/profiling-entity-framework-using-the-visual-studio-2010-profiler>. Posting ini ditulis untuk aplikasi windows. Jika Anda perlu membuat profil aplikasi web, alat Windows Performance Recorder (WPR) dan Windows Penganalisis Kinerja (WPA) mungkin berfungsi lebih baik daripada bekerja dari Visual Studio. WPR dan WPA adalah bagian dari Toolkit Performa Windows yang disertakan dengan Windows Assessment and Deployment Kit.

10.2 Pembuatan profil Aplikasi/Database

Alat seperti profiler yang disertakan dalam Visual Studio memberi tahu Anda di mana aplikasi Anda menghabiskan waktu.  Jenis profiler lain tersedia yang melakukan analisis dinamis aplikasi Anda yang sedang berjalan, baik dalam produksi atau pra-produksi tergantung pada kebutuhan, dan mencari jebakan umum dan anti-pola akses database.

Dua profiler yang tersedia secara komersial adalah Entity Framework Profiler ( <http://efprof.com>) dan ORMProfiler ( <http://ormprofiler.com>).

Jika aplikasi Anda adalah aplikasi MVC menggunakan Code First, Anda dapat menggunakan MiniProfiler StackExchange. Scott Hanselman menjelaskan alat ini dalam blognya di: <http://www.hanselman.com/blog/NuGetPackageOfTheWeek9ASPNETMiniProfilerFromStackExchangeRocksYourWorld.aspx>.

Untuk informasi selengkapnya tentang pembuatan profil aktivitas database aplikasi Anda, lihat artikel Majalah MSDN Julie Lerman berjudul Pembuatan Profil Aktivitas Database dalam Kerangka Kerja Entitas.

10.3 Pencatat database

Jika Anda menggunakan Entity Framework 6 juga pertimbangkan untuk menggunakan fungsionalitas pengelogan bawaan. Properti Database dari konteks dapat diinstruksikan untuk mencatat aktivitasnya melalui konfigurasi satu baris sederhana:

    using (var context = newQueryComparison.DbC.NorthwindEntities())
    {
        context.Database.Log = Console.WriteLine;
        var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");
        q.ToList();
    }

Dalam contoh ini aktivitas database akan dicatat ke konsol, tetapi properti Log dapat dikonfigurasi untuk memanggil delegasi string> Tindakan<apa pun.

Jika Anda ingin mengaktifkan pengelogan database tanpa kompilasi ulang, dan Anda menggunakan Entity Framework 6.1 atau yang lebih baru, Anda dapat melakukannya dengan menambahkan pencegat di file web.config atau app.config aplikasi Anda.

  <interceptors>
    <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
      <parameters>
        <parameter value="C:\Path\To\My\LogOutput.txt"/>
      </parameters>
    </interceptor>
  </interceptors>

Untuk informasi selengkapnya tentang cara menambahkan pengelogan tanpa kompilasi ulang, buka <http://blog.oneunicorn.com/2014/02/09/ef-6-1-turning-on-logging-without-recompiling/>.

11 Lampiran

11.1 A. Lingkungan Uji

Lingkungan ini menggunakan penyiapan 2 komputer dengan database pada komputer terpisah dari aplikasi klien. Mesin berada di rak yang sama, sehingga latensi jaringan relatif rendah, tetapi lebih realistis daripada lingkungan komputer tunggal.

11.1.1 App Server

11.1.1.1 Lingkungan Perangkat Lunak
  • Lingkungan Perangkat Lunak Entity Framework 4
    • Nama OS: Windows Server 2008 R2 Enterprise SP1.
    • Visual Studio 2010 – Ultimate.
    • Visual Studio 2010 SP1 (hanya untuk beberapa perbandingan).
  • Kerangka Kerja Entitas 5 dan 6 Lingkungan Perangkat Lunak
    • Nama OS: Windows 8.1 Enterprise
    • Visual Studio 2013 – Ultimate.
11.1.1.2 Lingkungan Perangkat Keras
  • Prosesor Ganda: Intel(R) Xeon(R) CPU L5520 W3530 @ 2,27GHz, 2261 Mhz8 GHz, 4 Core, 84 Prosesor Logis.
  • RamRAM 2412 GB.
  • 136 GB SCSI250GB drive SATA 7200 rpm 3GB/dtk dibagi menjadi 4 partisi.

Server 11.1.2 DB

11.1.2.1 Lingkungan Perangkat Lunak
  • Nama OS: Windows Server 2008 R28.1 Enterprise SP1.
  • SQL Server 2008 R22012.
11.1.2.2 Lingkungan Perangkat Keras
  • Prosesor Tunggal: Intel(R) Xeon(R) CPU L5520 @ 2.27GHz, 2261 MhzES-1620 0 @ 3.60GHz, 4 Core, 8 Prosesor Logis.
  • RamRAM 824 GB.
  • 465 GB ATA500GB drive SATA 7200 rpm 6GB/dtk dibagi menjadi 4 partisi.

11.2 B. Pengujian perbandingan performa kueri

Model Northwind digunakan untuk menjalankan pengujian ini. Ini dihasilkan dari database menggunakan perancang Kerangka Kerja Entitas. Kemudian, kode berikut digunakan untuk membandingkan performa opsi eksekusi kueri:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Entity.Infrastructure;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Linq;

namespace QueryComparison
{
    public partial class NorthwindEntities : ObjectContext
    {
        private static readonly Func<NorthwindEntities, string, IQueryable<Product>> productsForCategoryCQ = CompiledQuery.Compile(
            (NorthwindEntities context, string categoryName) =>
                context.Products.Where(p => p.Category.CategoryName == categoryName)
                );

        public IQueryable<Product> InvokeProductsForCategoryCQ(string categoryName)
        {
            return productsForCategoryCQ(this, categoryName);
        }
    }

    public class QueryTypePerfComparison
    {
        private static string entityConnectionStr = @"metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.msl;provider=System.Data.SqlClient;provider connection string='data source=.;initial catalog=Northwind;integrated security=True;multipleactiveresultsets=True;App=EntityFramework'";

        public void LINQIncludingContextCreation()
        {
            using (NorthwindEntities context = new NorthwindEntities())
            {                 
                var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");
                q.ToList();
            }
        }

        public void LINQNoTracking()
        {
            using (NorthwindEntities context = new NorthwindEntities())
            {
                context.Products.MergeOption = MergeOption.NoTracking;

                var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");
                q.ToList();
            }
        }

        public void CompiledQuery()
        {
            using (NorthwindEntities context = new NorthwindEntities())
            {
                var q = context.InvokeProductsForCategoryCQ("Beverages");
                q.ToList();
            }
        }

        public void ObjectQuery()
        {
            using (NorthwindEntities context = new NorthwindEntities())
            {
                ObjectQuery<Product> products = context.Products.Where("it.Category.CategoryName = 'Beverages'");
                products.ToList();
            }
        }

        public void EntityCommand()
        {
            using (EntityConnection eConn = new EntityConnection(entityConnectionStr))
            {
                eConn.Open();
                EntityCommand cmd = eConn.CreateCommand();
                cmd.CommandText = "Select p From NorthwindEntities.Products As p Where p.Category.CategoryName = 'Beverages'";

                using (EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
                {
                    List<Product> productsList = new List<Product>();
                    while (reader.Read())
                    {
                        DbDataRecord record = (DbDataRecord)reader.GetValue(0);

                        // 'materialize' the product by accessing each field and value. Because we are materializing products, we won't have any nested data readers or records.
                        int fieldCount = record.FieldCount;

                        // Treat all products as Product, even if they are the subtype DiscontinuedProduct.
                        Product product = new Product();  

                        product.ProductID = record.GetInt32(0);
                        product.ProductName = record.GetString(1);
                        product.SupplierID = record.GetInt32(2);
                        product.CategoryID = record.GetInt32(3);
                        product.QuantityPerUnit = record.GetString(4);
                        product.UnitPrice = record.GetDecimal(5);
                        product.UnitsInStock = record.GetInt16(6);
                        product.UnitsOnOrder = record.GetInt16(7);
                        product.ReorderLevel = record.GetInt16(8);
                        product.Discontinued = record.GetBoolean(9);

                        productsList.Add(product);
                    }
                }
            }
        }

        public void ExecuteStoreQuery()
        {
            using (NorthwindEntities context = new NorthwindEntities())
            {
                ObjectResult<Product> beverages = context.ExecuteStoreQuery<Product>(
@"    SELECT        P.ProductID, P.ProductName, P.SupplierID, P.CategoryID, P.QuantityPerUnit, P.UnitPrice, P.UnitsInStock, P.UnitsOnOrder, P.ReorderLevel, P.Discontinued
    FROM            Products AS P INNER JOIN Categories AS C ON P.CategoryID = C.CategoryID
    WHERE        (C.CategoryName = 'Beverages')"
);
                beverages.ToList();
            }
        }

        public void ExecuteStoreQueryDbContext()
        {
            using (var context = new QueryComparison.DbC.NorthwindEntities())
            {
                var beverages = context.Database.SqlQuery\<QueryComparison.DbC.Product>(
@"    SELECT        P.ProductID, P.ProductName, P.SupplierID, P.CategoryID, P.QuantityPerUnit, P.UnitPrice, P.UnitsInStock, P.UnitsOnOrder, P.ReorderLevel, P.Discontinued
    FROM            Products AS P INNER JOIN Categories AS C ON P.CategoryID = C.CategoryID
    WHERE        (C.CategoryName = 'Beverages')"
);
                beverages.ToList();
            }
        }

        public void ExecuteStoreQueryDbSet()
        {
            using (var context = new QueryComparison.DbC.NorthwindEntities())
            {
                var beverages = context.Products.SqlQuery(
@"    SELECT        P.ProductID, P.ProductName, P.SupplierID, P.CategoryID, P.QuantityPerUnit, P.UnitPrice, P.UnitsInStock, P.UnitsOnOrder, P.ReorderLevel, P.Discontinued
    FROM            Products AS P INNER JOIN Categories AS C ON P.CategoryID = C.CategoryID
    WHERE        (C.CategoryName = 'Beverages')"
);
                beverages.ToList();
            }
        }

        public void LINQIncludingContextCreationDbContext()
        {
            using (var context = new QueryComparison.DbC.NorthwindEntities())
            {                 
                var q = context.Products.Where(p => p.Category.CategoryName == "Beverages");
                q.ToList();
            }
        }

        public void LINQNoTrackingDbContext()
        {
            using (var context = new QueryComparison.DbC.NorthwindEntities())
            {
                var q = context.Products.AsNoTracking().Where(p => p.Category.CategoryName == "Beverages");
                q.ToList();
            }
        }
    }
}

11.3 C. Model Navision

Database Navision adalah database besar yang digunakan untuk demo Microsoft Dynamics – NAV. Model konseptual yang dihasilkan berisi 1005 set entitas dan 4227 set asosiasi. Model yang digunakan dalam pengujian adalah "datar" - tidak ada warisan yang ditambahkan ke dalamnya.

11.3.1 Kueri yang digunakan untuk pengujian Navision

Daftar kueri yang digunakan dengan model Navision berisi 3 kategori kueri Entity SQL:

11.3.1.1 Pencarian

Kueri pencarian sederhana tanpa agregasi

  • Jumlah: 16232
  • Contoh:
  <Query complexity="Lookup">
    <CommandText>Select value distinct top(4) e.Idle_Time From NavisionFKContext.Session as e</CommandText>
  </Query>
11.3.1.2 SingleAggregating

Kueri BI normal dengan beberapa agregasi, tetapi tidak ada subtotal (kueri tunggal)

  • Jumlah: 2313
  • Contoh:
  <Query complexity="SingleAggregating">
    <CommandText>NavisionFK.MDF_SessionLogin_Time_Max()</CommandText>
  </Query>

Di mana MDF_SessionLogin_Time_Max() didefinisikan dalam model sebagai:

  <Function Name="MDF_SessionLogin_Time_Max" ReturnType="Collection(DateTime)">
    <DefiningExpression>SELECT VALUE Edm.Min(E.Login_Time) FROM NavisionFKContext.Session as E</DefiningExpression>
  </Function>
11.3.1.3 MengagregasiSubtotals

Kueri BI dengan agregasi dan subtotal (melalui gabungan semua)

  • Hitungan: 178
  • Contoh:
  <Query complexity="AggregatingSubtotals">
    <CommandText>
using NavisionFK;
function AmountConsumed(entities Collection([CRONUS_International_Ltd__Zone])) as
(
    Edm.Sum(select value N.Block_Movement FROM entities as E, E.CRONUS_International_Ltd__Bin as N)
)
function AmountConsumed(P1 Edm.Int32) as
(
    AmountConsumed(select value e from NavisionFKContext.CRONUS_International_Ltd__Zone as e where e.Zone_Ranking = P1)
)
----------------------------------------------------------------------------------------------------------------------
(
    select top(10) Zone_Ranking, Cross_Dock_Bin_Zone, AmountConsumed(GroupPartition(E))
    from NavisionFKContext.CRONUS_International_Ltd__Zone as E
    where AmountConsumed(E.Zone_Ranking) > @MinAmountConsumed
    group by E.Zone_Ranking, E.Cross_Dock_Bin_Zone
)
union all
(
    select top(10) Zone_Ranking, Cast(null as Edm.Byte) as P2, AmountConsumed(GroupPartition(E))
    from NavisionFKContext.CRONUS_International_Ltd__Zone as E
    where AmountConsumed(E.Zone_Ranking) > @MinAmountConsumed
    group by E.Zone_Ranking
)
union all
{
    Row(Cast(null as Edm.Int32) as P1, Cast(null as Edm.Byte) as P2, AmountConsumed(select value E
                                                                         from NavisionFKContext.CRONUS_International_Ltd__Zone as E
                                                                         where AmountConsumed(E.Zone_Ranking) > @MinAmountConsumed))
}</CommandText>
    <Parameters>
      <Parameter Name="MinAmountConsumed" DbType="Int32" Value="10000" />
    </Parameters>
  </Query>