Topik Performa Tingkat Lanjut

Pengumpulan DbContext

Umumnya DbContext adalah objek ringan: membuat dan membuang objek tidak melibatkan operasi database, dan sebagian besar aplikasi dapat melakukannya tanpa dampak yang nyata pada performa. Namun, setiap instans konteks memang menyiapkan berbagai layanan internal dan objek yang diperlukan untuk melakukan tugasnya, dan overhead terus-menerus melakukannya mungkin signifikan dalam skenario performa tinggi. Untuk kasus ini, EF Core dapat mengumpulkan instans konteks Anda: ketika Anda membuang konteks Anda, EF Core mengatur ulang statusnya dan menyimpannya di kumpulan internal; ketika instans baru diminta berikutnya, instans yang dikumpulkan dikembalikan alih-alih menyiapkan yang baru. Pengumpulan konteks memungkinkan Anda membayar biaya penyiapan konteks hanya sekali pada startup program, bukan terus menerus.

Perhatikan bahwa pengumpulan konteks bersifat ortogonal untuk pengumpulan koneksi database, yang dikelola pada tingkat yang lebih rendah dalam driver database.

Pola umum dalam aplikasi ASP.NET Core menggunakan EF Core melibatkan pendaftaran jenis kustom DbContext ke dalam kontainer injeksi dependensi melalui AddDbContext. Kemudian, instans jenis tersebut diperoleh melalui parameter konstruktor di pengontrol atau Halaman Razor.

Untuk mengaktifkan pengumpulan konteks, cukup ganti AddDbContext dengan AddDbContextPool:

builder.Services.AddDbContextPool<WeatherForecastContext>(
    o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));

Parameter poolSizeAddDbContextPool mengatur jumlah maksimum instans yang dipertahankan oleh kumpulan (default ke 1024). Setelah poolSize terlampaui, instans konteks baru tidak di-cache dan EF kembali ke perilaku non-pengumpulan pembuatan instans sesuai permintaan.

Tolak ukur

Berikut ini adalah hasil tolok ukur untuk mengambil satu baris dari database SQL Server yang berjalan secara lokal pada komputer yang sama, dengan dan tanpa pengumpulan konteks. Seperti biasa, hasil akan berubah dengan jumlah baris, latensi ke server database Anda, dan faktor lainnya. Yang penting, ini tolok ukur performa pengumpulan utas tunggal, sementara skenario yang dipertaruhkan di dunia nyata mungkin memiliki hasil yang berbeda; tolok ukur pada platform Anda sebelum membuat keputusan apa pun. Kode sumber tersedia di sini, jangan ragu untuk menggunakannya sebagai dasar untuk pengukuran Anda sendiri.

Metode NumBlogs Rata-rata Kesalahan StdDev Gen 0 Gen 1 Gen 2 Dialokasikan
TanpaContextPooling 1 701.6 kami 26.62 kami 78.48 kami 11.7188 - - 50,38 KB
DenganContextPooling 1 350.1 kami 6.80 kami 14.64 kami 0.9766 - - 4,63 KB

Mengelola status dalam konteks terkumpul

Pengumpulan konteks berfungsi dengan menggunakan kembali instans konteks yang sama di seluruh permintaan; ini berarti bahwa instans terdaftar secara efektif sebagai Singleton, dan instans yang sama digunakan kembali di beberapa permintaan (atau cakupan DI). Ini berarti bahwa perawatan khusus harus diambil ketika konteks melibatkan status apa pun yang dapat berubah di antara permintaan. Sangat penting, konteks OnConfiguring hanya dipanggil sekali - ketika konteks instans pertama kali dibuat - sehingga tidak dapat digunakan untuk mengatur status yang perlu bervariasi (misalnya ID penyewa).

Skenario umum yang melibatkan status konteks akan menjadi aplikasi multi-penyewa ASP.NET Core, di mana instans konteks memiliki ID penyewa yang diperhitungkan oleh kueri (lihat Filter Kueri Global untuk detail selengkapnya). Karena ID penyewa perlu berubah dengan setiap permintaan web, kita perlu melalui beberapa langkah tambahan untuk membuatnya berfungsi dengan pengumpulan konteks.

Mari kita asumsikan bahwa aplikasi Anda mendaftarkan layanan tercakup ITenant , yang membungkus ID penyewa dan informasi terkait penyewa lainnya:

// Below is a minimal tenant resolution strategy, which registers a scoped ITenant service in DI.
// In this sample, we simply accept the tenant ID as a request query, which means that a client can impersonate any
// tenant. In a real application, the tenant ID would be set based on secure authentication data.
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenant>(sp =>
{
    var tenantIdString = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request.Query["TenantId"];

    return tenantIdString != StringValues.Empty && int.TryParse(tenantIdString, out var tenantId)
        ? new Tenant(tenantId)
        : null;
});

Seperti yang ditulis di atas, perhatikan dari mana Anda mendapatkan ID penyewa - ini adalah aspek penting dari keamanan aplikasi Anda.

Setelah kami memiliki layanan cakupan ITenant kami, daftarkan pabrik konteks pengumpulan sebagai layanan Singleton, seperti biasa:

builder.Services.AddPooledDbContextFactory<WeatherForecastContext>(
    o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));

Selanjutnya, tulis pabrik konteks kustom yang mendapatkan konteks terkumpul dari pabrik Singleton yang kami daftarkan, dan menyuntikkan ID penyewa ke dalam instans konteks yang diserahkannya:

public class WeatherForecastScopedFactory : IDbContextFactory<WeatherForecastContext>
{
    private const int DefaultTenantId = -1;

    private readonly IDbContextFactory<WeatherForecastContext> _pooledFactory;
    private readonly int _tenantId;

    public WeatherForecastScopedFactory(
        IDbContextFactory<WeatherForecastContext> pooledFactory,
        ITenant tenant)
    {
        _pooledFactory = pooledFactory;
        _tenantId = tenant?.TenantId ?? DefaultTenantId;
    }

    public WeatherForecastContext CreateDbContext()
    {
        var context = _pooledFactory.CreateDbContext();
        context.TenantId = _tenantId;
        return context;
    }
}

Setelah kami memiliki pabrik konteks kustom kami, daftarkan sebagai layanan Cakupan:

builder.Services.AddScoped<WeatherForecastScopedFactory>();

Terakhir, atur konteks untuk disuntikkan dari pabrik Cakupan kami:

builder.Services.AddScoped(
    sp => sp.GetRequiredService<WeatherForecastScopedFactory>().CreateDbContext());

Sebagai titik ini, pengontrol Anda secara otomatis disuntikkan dengan instans konteks yang memiliki ID penyewa yang tepat, tanpa harus tahu apa pun tentang hal itu.

Kode sumber lengkap untuk sampel ini tersedia di sini.

Catatan

Meskipun EF Core mengurus pengaturan ulang status internal untuk DbContext dan layanan terkaitnya, EF umumnya tidak mengatur ulang status dalam driver database yang mendasar, yang berada di luar EF. Misalnya, jika Anda membuka dan menggunakan DbConnection status ADO.NET secara manual, terserah Anda untuk memulihkan status tersebut sebelum mengembalikan instans konteks ke kumpulan, misalnya dengan menutup koneksi. Kegagalan untuk melakukannya dapat menyebabkan status bocor di seluruh permintaan yang tidak terkait.

Kueri yang dikompilasi

Ketika EF menerima pohon kueri LINQ untuk eksekusi, EF harus terlebih dahulu "mengkompilasi" pohon itu, misalnya menghasilkan SQL darinya. Karena tugas ini adalah proses yang berat, EF menyimpan kueri berdasarkan bentuk pohon kueri, sehingga kueri dengan struktur yang sama menggunakan kembali output kompilasi yang di-cache secara internal. Penembolokan ini memastikan bahwa menjalankan kueri LINQ yang sama beberapa kali sangat cepat, bahkan jika nilai parameter berbeda.

Namun, EF masih harus melakukan tugas tertentu sebelum dapat menggunakan cache kueri internal. Misalnya, pohon ekspresi kueri Anda harus dibandingkan secara rekursif dengan pohon ekspresi kueri yang di-cache, untuk menemukan kueri cache yang benar. Overhead untuk pemrosesan awal ini dapat diabaikan di sebagian besar aplikasi EF, terutama jika dibandingkan dengan biaya lain yang terkait dengan eksekusi kueri (I/O jaringan, pemrosesan kueri aktual dan I/O disk di database...). Namun, dalam skenario performa tinggi tertentu, mungkin diinginkan untuk menghilangkannya.

EF mendukung kueri yang dikompilasi, yang memungkinkan kompilasi eksplisit kueri LINQ ke dalam delegasi .NET. Setelah delegasi ini diperoleh, delegasi ini dapat dipanggil langsung untuk menjalankan kueri, tanpa menyediakan pohon ekspresi LINQ. Teknik ini melewati pencarian cache, dan menyediakan cara yang paling dioptimalkan untuk menjalankan kueri di EF Core. Berikut ini adalah beberapa hasil tolok ukur yang membandingkan performa kueri yang dikompilasi dan tidak dikompilasi; tolok ukur pada platform Anda sebelum membuat keputusan apa pun. Kode sumber tersedia di sini, jangan ragu untuk menggunakannya sebagai dasar untuk pengukuran Anda sendiri.

Metode NumBlogs Rata-rata Kesalahan StdDev Gen 0 Dialokasikan
WithCompiledQuery 1 564.2 kami 6.75 kami 5.99 kami 1.9531 9 KB
TanpaCompiledQuery 1 671.6 kami 12.72 kami 16.54 kami 2.9297 13 KB
WithCompiledQuery 10 645.3 kami 10.00 kami 9.35 kami 2.9297 13 KB
TanpaCompiledQuery 10 709.8 kami 25.20 kami 73.10 kami 3.9063 18 KB

Untuk menggunakan kueri yang dikompilasi, pertama-tama kompilasi kueri dengan EF.CompileAsyncQuery sebagai berikut (gunakan EF.CompileQuery untuk kueri sinkron):

private static readonly Func<BloggingContext, int, IAsyncEnumerable<Blog>> _compiledQuery
    = EF.CompileAsyncQuery(
        (BloggingContext context, int length) => context.Blogs.Where(b => b.Url.StartsWith("http://") && b.Url.Length == length));

Dalam sampel kode ini, kami menyediakan EF dengan lambda yang DbContext menerima instans, dan parameter arbitrer untuk diteruskan ke kueri. Sekarang Anda dapat memanggil delegasi tersebut setiap kali Anda ingin menjalankan kueri:

await foreach (var blog in _compiledQuery(context, 8))
{
    // Do something with the results
}

Perhatikan bahwa delegasi aman untuk utas, dan dapat dipanggil secara bersamaan pada instans konteks yang berbeda.

Batasan

  • Kueri yang dikompilasi hanya dapat digunakan terhadap satu model EF Core. Instans konteks yang berbeda dari jenis yang sama terkadang dapat dikonfigurasi untuk menggunakan model yang berbeda; menjalankan kueri yang dikompilasi dalam skenario ini tidak didukung.
  • Saat menggunakan parameter dalam kueri yang dikompilasi, gunakan parameter skalar sederhana. Ekspresi parameter yang lebih kompleks - seperti akses anggota/metode pada instans - tidak didukung.

Penembolokan dan parameterisasi kueri

Ketika EF menerima pohon kueri LINQ untuk eksekusi, EF harus terlebih dahulu "mengkompilasi" pohon itu, misalnya menghasilkan SQL darinya. Karena tugas ini adalah proses yang berat, EF menyimpan kueri berdasarkan bentuk pohon kueri, sehingga kueri dengan struktur yang sama menggunakan kembali output kompilasi yang di-cache secara internal. Penembolokan ini memastikan bahwa menjalankan kueri LINQ yang sama beberapa kali sangat cepat, bahkan jika nilai parameter berbeda.

Pertimbangkan dua kueri berikut:

var post1 = context.Posts.FirstOrDefault(p => p.Title == "post1");
var post2 = context.Posts.FirstOrDefault(p => p.Title == "post2");

Karena pohon ekspresi berisi konstanta yang berbeda, pohon ekspresi berbeda dan masing-masing kueri ini akan dikompilasi secara terpisah oleh EF Core. Selain itu, setiap kueri menghasilkan perintah SQL yang sedikit berbeda:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog1'

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog2'

Karena SQL berbeda, server database Anda kemungkinan juga perlu menghasilkan rencana kueri untuk kedua kueri, daripada menggunakan kembali paket yang sama.

Modifikasi kecil pada kueri Anda dapat mengubah banyak hal:

var postTitle = "post1";
var post1 = context.Posts.FirstOrDefault(p => p.Title == postTitle);
postTitle = "post2";
var post2 = context.Posts.FirstOrDefault(p => p.Title == postTitle);

Karena nama blog sekarang diparameterkan, kedua kueri memiliki bentuk pohon yang sama, dan EF hanya perlu dikompilasi sekali. SQL yang diproduksi juga diparameterkan, memungkinkan database untuk menggunakan kembali rencana kueri yang sama:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = @__blogName_0

Perhatikan bahwa tidak perlu membuat parameter masing-masing dan setiap kueri: sangat baik untuk memiliki beberapa kueri dengan konstanta, dan memang, database (dan EF) terkadang dapat melakukan pengoptimalan tertentu di sekitar konstanta yang tidak dimungkinkan ketika kueri diparameterkan. Lihat bagian tentang kueri yang dibangun secara dinamis untuk contoh di mana parameterisasi yang tepat sangat penting.

Catatan

Penghitung peristiwa EF Core melaporkan Tingkat Hit Cache Kueri. Dalam aplikasi normal, penghitung ini mencapai 100% segera setelah startup program, setelah sebagian besar kueri dijalankan setidaknya sekali. Jika penghitung ini tetap stabil di bawah 100%, itu adalah indikasi bahwa aplikasi Anda mungkin melakukan sesuatu yang mengalahkan cache kueri - ada baiknya untuk menyelidikinya.

Catatan

Bagaimana database mengelola rencana kueri cache bergantung pada database. Misalnya, SQL Server secara implisit mempertahankan cache rencana kueri LRU, sedangkan PostgreSQL tidak (tetapi pernyataan yang disiapkan dapat menghasilkan efek akhir yang sangat mirip). Lihat dokumentasi database Anda untuk detail selengkapnya.

Kueri yang dibangun secara dinamis

Dalam beberapa situasi, perlu untuk membangun kueri LINQ secara dinamis daripada menentukannya langsung dalam kode sumber. Ini dapat terjadi, misalnya, di situs web yang menerima detail kueri arbitrer dari klien, dengan operator kueri terbuka (pengurutan, pemfilteran, penomoran...). Pada prinsipnya, jika dilakukan dengan benar, kueri yang dibangun secara dinamis bisa seefisien kueri biasa (meskipun tidak mungkin menggunakan pengoptimalan kueri yang dikompilasi dengan kueri dinamis). Namun, dalam praktiknya, mereka sering menjadi sumber masalah performa, karena mudah untuk secara tidak sengaja menghasilkan pohon ekspresi dengan bentuk yang berbeda setiap saat.

Contoh berikut menggunakan tiga teknik untuk membuat ekspresi lambda kueri Where :

  1. API Ekspresi dengan konstanta: Membangun ekspresi secara dinamis dengan Api Ekspresi, menggunakan simpul konstanta. Ini adalah kesalahan yang sering terjadi ketika membangun pohon ekspresi secara dinamis, dan menyebabkan EF mengkompilasi ulang kueri setiap kali dipanggil dengan nilai konstanta yang berbeda (biasanya juga menyebabkan polusi cache rencana di server database).
  2. API Ekspresi dengan parameter: Versi yang lebih baik, yang mengganti konstanta dengan parameter. Ini memastikan bahwa kueri hanya dikompilasi sekali terlepas dari nilai yang disediakan, dan SQL yang sama (berparameter) dihasilkan.
  3. Sederhana dengan parameter: Versi yang tidak menggunakan Api Ekspresi, untuk perbandingan, yang membuat pohon yang sama dengan metode di atas tetapi jauh lebih sederhana. Dalam banyak kasus, dimungkinkan untuk membangun pohon ekspresi Anda secara dinamis tanpa menggunakan API Ekspresi, yang mudah salah.

Kami menambahkan Where operator ke kueri hanya jika parameter yang diberikan tidak null. Perhatikan bahwa ini bukan kasus penggunaan yang baik untuk membangun kueri secara dinamis - tetapi kami menggunakannya untuk kesederhanaan:

[Benchmark]
public int ExpressionApiWithConstant()
{
    var url = "blog" + Interlocked.Increment(ref _blogNumber);
    using var context = new BloggingContext();

    IQueryable<Blog> query = context.Blogs;

    if (_addWhereClause)
    {
        var blogParam = Expression.Parameter(typeof(Blog), "b");
        var whereLambda = Expression.Lambda<Func<Blog, bool>>(
            Expression.Equal(
                Expression.MakeMemberAccess(
                    blogParam,
                    typeof(Blog).GetMember(nameof(Blog.Url)).Single()),
                Expression.Constant(url)),
            blogParam);

        query = query.Where(whereLambda);
    }

    return query.Count();
}

Tolok ukur kedua teknik ini memberikan hasil berikut:

Metode Rata-rata Kesalahan StdDev Gen0 Gen1 Dialokasikan
ExpressionApiWithConstant 1,665.8 kami 56.99 kami 163.5 kami 15.6250 - 109,92 KB
ExpressionApiWithParameter 757.1 kami 35.14 kami 103.6 kami 12.6953 0.9766 54,95 KB
SimpleWithParameter 760.3 kami 37.99 kami 112.0 kami 12.6953 - 55,03 KB

Bahkan jika perbedaan sub-milidetik tampak kecil, perlu diingat bahwa versi konstan terus-menerus mencemari cache dan menyebabkan kueri lain dikompilasi ulang, memperlambatnya juga dan memiliki dampak negatif umum pada performa Anda secara keseluruhan. Sangat disarankan untuk menghindari kompilasi ulang kueri konstan.

Catatan

Hindari membuat kueri dengan API pohon ekspresi kecuali Anda benar-benar perlu. Selain kompleksitas API, sangat mudah untuk secara tidak sengaja menyebabkan masalah performa yang signifikan saat menggunakannya.

Model yang dikompilasi

Model yang dikompilasi dapat meningkatkan waktu mulai EF Core untuk aplikasi dengan model besar. Model besar biasanya berarti ratusan hingga ribuan jenis dan hubungan entitas. Waktu mulai di sini adalah waktu untuk melakukan operasi pertama pada DbContext saat DbContext jenis tersebut digunakan untuk pertama kalinya dalam aplikasi. Perhatikan bahwa hanya membuat DbContext instans tidak menyebabkan model EF diinisialisasi. Sebagai gantinya, operasi pertama umum yang menyebabkan model diinisialisasi termasuk memanggil DbContext.Add atau menjalankan kueri pertama.

Model yang dikompilasi dibuat menggunakan dotnet ef alat baris perintah. Pastikan Anda telah menginstal versi terbaru alat sebelum melanjutkan.

Perintah baru dbcontext optimize digunakan untuk menghasilkan model yang dikompilasi. Contohnya:

dotnet ef dbcontext optimize

Opsi --output-dir dan --namespace dapat digunakan untuk menentukan direktori dan namespace tempat model yang dikompilasi akan dihasilkan. Contohnya:

PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels> dotnet ef dbcontext optimize --output-dir MyCompiledModels --namespace MyCompiledModels
Build started...
Build succeeded.
Successfully generated a compiled model, to use it call 'options.UseModel(MyCompiledModels.BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels>

Output dari menjalankan perintah ini mencakup sepotong kode untuk disalin dan ditempelkan ke dalam konfigurasi Anda DbContext untuk menyebabkan EF Core menggunakan model yang dikompilasi. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseModel(MyCompiledModels.BlogsContextModel.Instance)
        .UseSqlite(@"Data Source=test.db");

Bootstrapping model yang dikompilasi

Biasanya tidak perlu melihat kode bootstrapping yang dihasilkan. Namun, terkadang dapat berguna untuk menyesuaikan model atau pemuatannya. Kode bootstrapping terlihat seperti ini:

[DbContext(typeof(BlogsContext))]
partial class BlogsContextModel : RuntimeModel
{
    private static BlogsContextModel _instance;
    public static IModel Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new BlogsContextModel();
                _instance.Initialize();
                _instance.Customize();
            }

            return _instance;
        }
    }

    partial void Initialize();

    partial void Customize();
}

Ini adalah kelas parsial dengan metode parsial yang dapat diimplementasikan untuk menyesuaikan model sesuai kebutuhan.

Selain itu, beberapa model yang dikompilasi dapat dihasilkan untuk DbContext jenis yang dapat menggunakan model yang berbeda tergantung pada beberapa konfigurasi runtime. Ini harus ditempatkan ke dalam folder dan namespace yang berbeda, seperti yang ditunjukkan di atas. Informasi runtime, seperti string koneksi, kemudian dapat diperiksa dan model yang benar dikembalikan sesuai kebutuhan. Contohnya:

public static class RuntimeModelCache
{
    private static readonly ConcurrentDictionary<string, IModel> _runtimeModels
        = new();

    public static IModel GetOrCreateModel(string connectionString)
        => _runtimeModels.GetOrAdd(
            connectionString, cs =>
            {
                if (cs.Contains("X"))
                {
                    return BlogsContextModel1.Instance;
                }

                if (cs.Contains("Y"))
                {
                    return BlogsContextModel2.Instance;
                }

                throw new InvalidOperationException("No appropriate compiled model found.");
            });
}

Batasan

Model yang dikompilasi memiliki beberapa batasan:

Karena keterbatasan ini, Anda hanya boleh menggunakan model yang dikompilasi jika waktu mulai EF Core Anda terlalu lambat. Mengkompilasi model kecil biasanya tidak sepadan.

Jika mendukung salah satu fitur ini sangat penting untuk keberhasilan Anda, pilih masalah yang sesuai yang ditautkan di atas.

Mengurangi overhead runtime

Seperti halnya lapisan apa pun, EF Core menambahkan sedikit overhead runtime dibandingkan dengan pengkodean langsung terhadap API database tingkat bawah. Overhead runtime ini tidak mungkin berdampak pada sebagian besar aplikasi dunia nyata dengan cara yang signifikan; topik lain dalam panduan performa ini, seperti efisiensi kueri, penggunaan indeks, dan meminimalkan perjalanan pulang pergi, jauh lebih penting. Selain itu, bahkan untuk aplikasi yang sangat dioptimalkan, latensi jaringan dan I/O database biasanya akan mendominasi waktu yang dihabiskan di dalam EF Core itu sendiri. Namun, untuk aplikasi berkinerja tinggi dan latensi rendah di mana setiap bit perf penting, rekomendasi berikut dapat digunakan untuk mengurangi overhead EF Core seminimal mungkin:

  • Aktifkan pengumpulan DbContext; tolok ukur kami menunjukkan bahwa fitur ini dapat berdampak menentukan pada aplikasi latensi rendah dengan perf tinggi.
    • Pastikan bahwa maxPoolSize sesuai dengan skenario penggunaan Anda; jika terlalu rendah, DbContext instans akan terus dibuat dan dibuang, menurunkan performa. Mengaturnya terlalu tinggi mungkin tidak perlu mengonsumsi memori karena instans yang tidak digunakan DbContext dipertahankan di kumpulan.
    • Untuk peningkatan perf kecil tambahan, pertimbangkan untuk menggunakan PooledDbContextFactory alih-alih memiliki instans konteks injeksi DI secara langsung. Manajemen DI pengumpulan DbContext menimbulkan sedikit overhead.
  • Gunakan kueri yang telah dikompresi untuk kueri panas.
    • Semakin kompleks kueri LINQ - semakin banyak operator yang dikandungnya dan semakin besar pohon ekspresi yang dihasilkan - semakin banyak keuntungan yang dapat diharapkan dari menggunakan kueri yang dikompilasi.
  • Pertimbangkan untuk menonaktifkan pemeriksaan keamanan utas dengan mengatur EnableThreadSafetyChecks ke false dalam konfigurasi konteks Anda.
    • Menggunakan instans yang sama DbContext secara bersamaan dari utas yang berbeda tidak didukung. EF Core memiliki fitur keamanan yang mendeteksi bug pemrograman ini dalam banyak kasus (tetapi tidak semua), dan segera memberikan pengecualian informatif. Namun, fitur keamanan ini menambahkan beberapa overhead runtime.
    • PERINGATAN: Hanya nonaktifkan pemeriksaan keamanan utas setelah menguji secara menyeluruh bahwa aplikasi Anda tidak berisi bug konkurensi tersebut.