Bagikan melalui


Masa Pakai, Konfigurasi, dan Inisialisasi DbContext

Artikel ini memperlihatkan pola dasar untuk inisialisasi dan konfigurasi instans DbContext.

Masa pakai DbContext

Masa pakai DbContext dimulai ketika instans dibuat dan berakhir saat instans dibuang. Instans DbContext dirancang untuk digunakan untuk satuunit kerja. Ini berarti bahwa masa pakai instans DbContext biasanya sangat singkat.

Tip

Untuk mengutip Martin Fowler dari tautan di atas, "Unit Kerja melacak semua yang Anda lakukan selama transaksi bisnis yang dapat memengaruhi database. Setelah selesai, ia mencari tahu semua yang perlu dilakukan untuk mengubah database sebagai hasil dari pekerjaan Anda."

Unit kerja umum saat menggunakan Entity Framework Core (EF Core) melibatkan:

Penting

  • Sangat penting untuk membuang DbContext setelah digunakan. Ini memastikan bahwa sumber daya yang tidak dikelola dibebaskan, dan bahwa setiap peristiwa atau kait lainnya tidak terdaftar sehingga mencegah kebocoran memori jika instans tetap direferensikan.
  • DbContext tidak aman untuk utas. Jangan bagikan konteks antar utas. Pastikan untuk menunggu semua panggilan asinkron sebelum terus menggunakan instans konteks.
  • Kode InvalidOperationException EF Core yang dilemparkan dapat menempatkan konteks ke dalam status yang tidak dapat dipulihkan. Pengecualian tersebut menunjukkan kesalahan program dan tidak dirancang untuk dipulihkan.

DbContext dalam injeksi dependensi untuk ASP.NET Core

Di banyak aplikasi web, setiap permintaan HTTP sesuai dengan satu unit kerja. Ini membuat mengikat masa pakai konteks dengan permintaan default yang baik untuk aplikasi web.

ASP.NET Core dikonfigurasi menggunakan injeksi dependensi. EF Core dapat ditambahkan ke konfigurasi ini menggunakan AddDbContext dalam ConfigureServices metode Startup.cs. Contohnya:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ApplicationDbContext>(
        options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}

Contoh ini mendaftarkan subkelas yang DbContext disebut ApplicationDbContext sebagai layanan terlingkup di penyedia layanan aplikasi ASP.NET Core (alias kontainer injeksi dependensi). Konteks dikonfigurasi untuk menggunakan penyedia database SQL Server dan akan membaca string koneksi dari konfigurasi ASP.NET Core. Biasanya tidak masalah di mana dalam ConfigureServices panggilan ke AddDbContext dilakukan.

Kelas ApplicationDbContext harus mengekspos konstruktor publik dengan parameter DbContextOptions<ApplicationDbContext>. Beginilah cara konfigurasi konteks dari AddDbContext diteruskan ke DbContext. Contohnya:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

ApplicationDbContext kemudian dapat digunakan dalam pengontrol ASP.NET Core atau layanan lain melalui injeksi konstruktor. Contohnya:

public class MyController
{
    private readonly ApplicationDbContext _context;

    public MyController(ApplicationDbContext context)
    {
        _context = context;
    }
}

Hasil akhir adalah instans ApplicationDbContext yang dibuat untuk setiap permintaan dan diteruskan ke pengontrol untuk melakukan unit kerja sebelum dibuang saat permintaan berakhir.

Baca lebih lanjut di artikel ini untuk mempelajari selengkapnya tentang opsi konfigurasi. Selain itu, lihat Pengaktifan aplikasi di ASP.NET Core dan Injeksi ketergantungan di ASP.NET Core untuk informasi selengkapnya tentang konfigurasi dan injeksi ketergantungan di ASP.NET Core.

Inisialisasi DbContext sederhana dengan 'baru'

Instans DbContext dapat dibuat dengan cara .NET biasa, misalnya dengan new dalam C#. Konfigurasi dapat dilakukan dengan mengesampingkan metode OnConfiguring, atau dengan meneruskan opsi ke konstruktor. Contohnya:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

Pola ini juga memudahkan untuk meneruskan konfigurasi seperti string koneksi melalui konstruktor DbContext. Contohnya:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

    public ApplicationDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

Secara bergantian, DbContextOptionsBuilder dapat digunakan untuk membuat objek DbContextOptions yang kemudian diteruskan ke konstruktor DbContext. Ini memungkinkan DbContext dikonfigurasi untuk injeksi dependensi juga dibangun secara eksplisit. Misalnya, saat menggunakan ApplicationDbContext yang ditentukan untuk aplikasi web ASP.NET Core di atas:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

DbContextOptions dapat dibuat dan konstruktor dapat dipanggil secara eksplisit:

var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
    .Options;

using var context = new ApplicationDbContext(contextOptions);

Menggunakan pabrik DbContext (misalnya untuk Blazor)

Beberapa jenis aplikasi (misalnya ASP.NET Core Blazor) menggunakan injeksi dependensi tetapi tidak membuat cakupan layanan yang selaras dengan masa pakai yang diinginkan DbContext. Bahkan di mana penyelarasan seperti itu memang ada, aplikasi mungkin perlu melakukan beberapa unit kerja dalam cakupan ini. Misalnya, beberapa unit kerja dalam satu permintaan HTTP.

Dalam kasus ini, AddDbContextFactory dapat digunakan untuk mendaftarkan pabrik untuk pembuatan instans DbContext. Contohnya:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<ApplicationDbContext>(
        options =>
            options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}

Kelas ApplicationDbContext harus mengekspos konstruktor publik dengan parameter DbContextOptions<ApplicationDbContext>. Ini adalah pola yang sama seperti yang digunakan di bagian tradisional ASP.NET Core di atas.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

Pabrik DbContextFactory kemudian dapat digunakan di layanan lain melalui injeksi konstruktor. Contohnya:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

Pabrik yang disuntikkan kemudian dapat digunakan untuk membangun instans DbContext dalam kode layanan. Contohnya:

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

Perhatikan bahwa DbContext instans yang dibuat dengan cara ini tidak dikelola oleh penyedia layanan aplikasi dan oleh karena itu harus dibuang oleh aplikasi.

Lihat ASP.NET Core Blazor Server dengan Entity Framework Core untuk informasi selengkapnya tentang menggunakan EF Core dengan Blazor.

DbContextOptions

Titik awal untuk semua konfigurasi DbContext adalah DbContextOptionsBuilder. Ada tiga cara untuk mendapatkan penyusun ini:

  • Metode AddDbContext dalam dan terkait
  • Dalam OnConfiguring
  • Dibangun secara eksplisit dengan new

Contoh setiap ditunjukkan di bagian sebelumnya. Konfigurasi yang sama dapat diterapkan terlepas dari mana pembangun berasal. Selain itu, OnConfiguring selalu dipanggil terlepas dari bagaimana konteks dibangun. Ini berarti OnConfiguring dapat digunakan untuk melakukan konfigurasi tambahan bahkan ketika AddDbContext sedang digunakan.

Mengonfigurasi penyedia database

Setiap instans DbContext harus dikonfigurasi untuk menggunakan satu dan hanya satu penyedia database. (Instans subjenis yang DbContext berbeda dapat digunakan dengan penyedia database yang berbeda, tetapi satu instans hanya boleh menggunakan satu.) Penyedia database dikonfigurasi menggunakan panggilan tertentu Use*. Misalnya, untuk menggunakan penyedia database SQL Server:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

Metode Use* ini adalah metode ekstensi yang diterapkan oleh penyedia database. Ini berarti bahwa paket NuGet penyedia database harus diinstal sebelum metode ekstensi dapat digunakan.

Tip

Penyedia database EF Core memanfaatkan metode ekstensi secara ekstensif. Jika pengkompilasi menunjukkan bahwa metode tidak dapat ditemukan, pastikan bahwa paket NuGet penyedia diinstal dan Anda memiliki using Microsoft.EntityFrameworkCore; dalam kode Anda.

Tabel berikut ini berisi contoh untuk penyedia database umum.

Sistem database Konfigurasi contoh Paket NuGet
SQL Server atau Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
Database dalam memori EF Core .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL* .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB* .UseMySql(connectionString) Pomelo.EntityFrameworkCore.MySql
Oracle* .UseOracle(connectionString) Oracle.EntityFrameworkCore

*Penyedia database ini tidak dikirim oleh Microsoft. Lihat Penyedia Database untuk informasi selengkapnya tentang penyedia database.

Peringatan

Database dalam memori EF Core tidak dirancang untuk penggunaan produksi. Selain itu, ini mungkin bukan pilihan terbaik bahkan untuk pengujian. Lihat Kode Pengujian yang Menggunakan EF Core untuk informasi selengkapnya.

Lihat String Koneksi untuk informasi selengkapnya tentang menggunakan string koneksi dengan EF Core.

Konfigurasi opsional khusus untuk penyedia database dilakukan di penyusun khusus penyedia tambahan. Misalnya, menggunakan EnableRetryOnFailure untuk mengonfigurasi coba lagi untuk ketahanan koneksi saat menyambungkan ke Azure SQL:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test",
                providerOptions => { providerOptions.EnableRetryOnFailure(); });
    }
}

Tip

Penyedia database yang sama digunakan untuk SQL Server dan Azure SQL. Namun, disarankan agar ketahanan koneksi digunakan saat menghubungkan ke SQL Azure.

Lihat Penyedia Database untuk informasi selengkapnya tentang konfigurasi khusus penyedia.

Konfigurasi DbContext lainnya

Konfigurasi DbContext lainnya dapat dirantai baik sebelum atau sesudah (tidak ada bedanya yang mana) panggilan Use*. Misalnya, untuk mengaktifkan pengelogan data sensitif:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

Tabel berikut berisi contoh metode umum yang dipanggil pada DbContextOptionsBuilder.

Metode DbContextOptionsBuilder Apa fungsinya Pelajari lebih lanjut
UseQueryTrackingBehavior Mengatur perilaku pelacakan default untuk kueri Perilaku Pelacakan Kueri
LogTo Cara sederhana untuk mendapatkan log EF Core Pengelogan, peristiwa, dan diagnostik
UseLoggerFactory Mendaftarkan pabrik Microsoft.Extensions.Logging Pengelogan, peristiwa, dan diagnostik
EnableSensitiveDataLogging Menyertakan data aplikasi dalam pengecualian dan pengelogan Pengelogan, peristiwa, dan diagnostik
EnableDetailedErrors Kesalahan kueri yang lebih rinci (dengan mengorbankan performa) Pengelogan, peristiwa, dan diagnostik
ConfigureWarnings Abaikan atau lempar untuk peringatan dan peristiwa lainnya Pengelogan, peristiwa, dan diagnostik
AddInterceptors Mendaftarkan pencegat EF Core Pengelogan, peristiwa, dan diagnostik
UseLazyLoadingProxies Gunakan proksi dinamis untuk pemuatan lambat Pemuatan Lambat
UseChangeTrackingProxies Menggunakan proksi dinamis untuk pelacakan perubahan Segera hadir...

Catatan

UseLazyLoadingProxies dan UseChangeTrackingProxies merupakan metode ekstensi dari paket NuGet Microsoft.EntityFrameworkCore.Proxies. Semacam ini ". Panggilan UseSomething()" adalah cara yang disarankan untuk mengonfigurasi dan/atau menggunakan ekstensi EF Core yang terkandung dalam paket lain.

DbContextOptions versus DbContextOptions<TContext>

Sebagian besar DbContext subkelas yang menerima DbContextOptions harus menggunakan variasi generikDbContextOptions<TContext>. Contohnya:

public sealed class SealedApplicationDbContext : DbContext
{
    public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }
}

Ini memastikan bahwa opsi yang benar untuk subjenis tertentu DbContext diselesaikan dari injeksi dependensi, bahkan ketika beberapa subjenis DbContext terdaftar.

Tip

DbContext Anda tidak perlu disegel, tetapi penyegelan adalah praktik terbaik untuk melakukannya agar kelas tidak dirancang untuk diwarisi.

Namun, jika subjenis DbContext itu sendiri dimaksudkan untuk diwarisi, maka subjenis tersebut harus mengekspos konstruktor yang dilindungi yang mengambil DbContextOptions non-generik. Contohnya:

public abstract class ApplicationDbContextBase : DbContext
{
    protected ApplicationDbContextBase(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Ini memungkinkan beberapa subkelas konkret untuk memanggil konstruktor dasar ini menggunakan instans generik DbContextOptions<TContext> mereka yang berbeda. Contohnya:

public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
    public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
        : base(contextOptions)
    {
    }
}

public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
    public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
        : base(contextOptions)
    {
    }
}

Perhatikan bahwa ini adalah pola yang persis sama seperti ketika mewarisi dari DbContext secara langsung. Artinya, konstruktor DbContext itu sendiri menerima DbContextOptions non-generik karena alasan ini.

Subkelas DbContext yang dimaksudkan untuk dipakai dan diwarisi dari harus mengekspos kedua bentuk konstruktor. Contohnya:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }

    protected ApplicationDbContext(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Konfigurasi DbContext waktu desain

Alat waktu desain EF Core seperti untuk Migrasi EF Core harus dapat menemukan dan membuat instans kerja jenis DbContext untuk mengumpulkan detail tentang jenis entitas aplikasi dan bagaimana mereka memetakan ke skema database. Proses ini dapat otomatis selama alat dapat dengan mudah membuat DbContext sewaktu-waktu sehingga akan dikonfigurasi mirip dengan cara yang akan dikonfigurasi pada run-time.

Meskipun pola apa pun yang menyediakan informasi konfigurasi yang diperlukan untuk DbContext dapat berfungsi pada run-time, alat yang memerlukan penggunaan DbContext pada waktu desain hanya dapat berfungsi dengan sejumlah pola yang terbatas. Ini dibahas secara lebih rinci dalam Pembuatan Konteks Waktu Desain.

Menghindari masalah utas DbContext

Entity Framework Core tidak mendukung beberapa operasi paralel yang dijalankan pada instans DbContext yang sama. Ini termasuk eksekusi paralel kueri asinkron dan penggunaan bersamaan eksplisit dari beberapa utas. Oleh karena itu, selalu await panggilan asinkron segera, atau gunakan instans DbContext terpisah untuk operasi yang dijalankan secara paralel.

Saat EF Core mendeteksi upaya untuk menggunakan instans DbContext secara bersamaan, Anda akan melihat InvalidOperationException dengan pesan seperti ini:

Operasi kedua dimulai pada konteks ini sebelum operasi sebelumnya selesai. Hal ini biasanya disebabkan oleh utas yang berbeda menggunakan instans DbContext yang sama, namun anggota instans tidak dijamin aman untuk utas.

Ketika akses bersamaan tidak terdeteksi, itu dapat mengakibatkan perilaku yang tidak terdefinisi, crash aplikasi, dan kerusakan data.

Ada kesalahan umum yang secara tidak sengaja dapat menyebabkan akses bersamaan pada instans DbContext yang sama:

Jebakan operasi asinkron

Metode asinkron memungkinkan EF Core memulai operasi yang mengakses database dengan cara non-pemblokiran. Namun jika pemanggil tidak menunggu penyelesaian salah satu metode ini, dan melanjutkan untuk melakukan operasi lain pada DbContext, status DbContext dapat, (dan kemungkinan besar akan) rusak.

Selalu tunggu metode asinkron EF Core segera.

Berbagi instans DbContext secara implisit melalui injeksi dependensi

Metode ekstensi AddDbContext mendaftarkan DbContext jenis dengan masa pakai tercakup secara default.

Ini aman dari masalah akses bersamaan di sebagian besar aplikasi ASP.NET Core karena hanya ada satu utas yang mengeksekusi setiap permintaan klien pada waktu tertentu, dan karena setiap permintaan mendapat cakupan injeksi dependensi terpisah (dan oleh karena itu instans DbContext terpisah). Untuk model hosting Blazor Server, satu permintaan logis digunakan untuk mempertahankan sirkuit pengguna Blazor, dan dengan demikian hanya satu instans DbContext tercakup yang tersedia per sirkuit pengguna jika cakupan injeksi default digunakan.

Kode apa pun yang secara eksplisit menjalankan beberapa utas secara paralel harus memastikan bahwa instans DbContext tidak pernah diakses secara bersamaan.

Menggunakan injeksi ketergantungan, ini dapat dicapai dengan mendaftarkan konteks sebagai cakupan, dan membuat cakupan (menggunakan IServiceScopeFactory) untuk setiap utas, atau dengan mendaftarkan DbContext sebagai sementara (menggunakan kelebihan AddDbContext yang membutuhkan parameter ServiceLifetime).

Bacaan lainnya