Bagikan melalui


Ketahanan Koneksi

Ketahanan koneksi secara otomatis mencoba kembali perintah database yang gagal. Fitur ini dapat digunakan dengan database apa pun dengan menyediakan "strategi eksekusi", yang merangkum logika yang diperlukan untuk mendeteksi kegagalan dan mencoba kembali perintah. Penyedia EF Core dapat menyediakan strategi eksekusi yang disesuaikan dengan kondisi kegagalan database spesifik mereka dan kebijakan percobaan kembali yang optimal.

Sebagai contoh, penyedia SQL Server menyertakan strategi eksekusi yang khusus disesuaikan dengan SQL Server (termasuk SQL Azure). Ini mengetahui jenis pengecualian yang dapat dicoba kembali dan memiliki default yang masuk akal untuk percobaan ulang maksimum, penundaan antara percobaan ulang, dll.

Strategi eksekusi ditentukan saat mengonfigurasi opsi untuk konteks Anda. Biasanya, ini terdapat dalam metode OnConfiguring dari konteks turunan Anda:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
            options => options.EnableRetryOnFailure());
}

atau di Startup.cs untuk aplikasi ASP.NET Core:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<PicnicContext>(
        options => options.UseSqlServer(
            "<connection string>",
            providerOptions => providerOptions.EnableRetryOnFailure()));
}

Nota

Mengaktifkan upaya ulang saat kegagalan menyebabkan EF menyimpan sementara set hasil secara internal, yang dapat meningkatkan secara signifikan persyaratan memori untuk kueri yang mengembalikan set hasil yang besar. Lihat buffering dan streaming untuk detail lebih lanjut.

Strategi eksekusi kustom

Ada mekanisme untuk mendaftarkan strategi eksekusi kustom Anda sendiri jika Anda ingin mengubah salah satu default.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseMyProvider(
            "<connection string>",
            options => options.ExecutionStrategy(...));
}

Strategi dan transaksi eksekusi

Strategi eksekusi yang secara otomatis mencoba ulang jika terjadi kegagalan harus dapat mengulangi setiap operasi dalam blok percobaan ulang yang gagal. Ketika percobaan ulang diaktifkan, setiap operasi yang Anda lakukan melalui EF Core menjadi operasi yang dapat dicoba kembali sendiri. Artinya, setiap kueri dan setiap panggilan ke SaveChangesAsync() akan diulang sebagai satu kesatuan jika kegagalan sementara muncul.

Namun, jika kode Anda memulai transaksi menggunakan BeginTransactionAsync(), Anda mendefinisikan grup operasi Anda sendiri yang perlu diperlakukan sebagai satu kesatuan, dan semua yang ada di dalam transaksi perlu diputar kembali jika terjadi kegagalan. Anda akan menerima pengecualian seperti berikut ini jika Anda mencoba melakukan ini saat menggunakan strategi eksekusi:

InvalidOperationException: Strategi eksekusi yang dikonfigurasi 'SqlServerRetryingExecutionStrategy' tidak mendukung transaksi yang dimulai pengguna. Gunakan strategi eksekusi yang dikembalikan oleh 'DbContext.Database.CreateExecutionStrategy()' untuk menjalankan semua operasi dalam transaksi sebagai unit yang dapat dicoba kembali.

Solusinya adalah memanggil strategi eksekusi secara manual dengan delegasi yang mewakili semua yang perlu dijalankan. Jika kegagalan sementara terjadi, strategi eksekusi akan memanggil delegasi lagi.


using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();

await strategy.ExecuteAsync(
    async () =>
    {
        using var context = new BloggingContext();
        await using var transaction = await context.Database.BeginTransactionAsync();

        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        await context.SaveChangesAsync();

        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
        await context.SaveChangesAsync();

        await transaction.CommitAsync();
    });

Pendekatan ini juga dapat digunakan dengan transaksi lingkungan.


using var context1 = new BloggingContext();
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });

var strategy = context1.Database.CreateExecutionStrategy();

await strategy.ExecuteAsync(
    async () =>
    {
        using var context2 = new BloggingContext();
        using var transaction = new TransactionScope();

        context2.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        await context2.SaveChangesAsync();

        await context1.SaveChangesAsync();

        transaction.Complete();
    });

Kegagalan penerapan transaksi dan masalah idempotensi

Secara umum, ketika ada kegagalan koneksi, transaksi saat ini digulung balik. Namun, jika koneksi terputus saat transaksi sedang dilakukan, status transaksi yang dihasilkan tidak diketahui.

Secara default, strategi eksekusi akan mencoba kembali operasi seolah-olah transaksi digulung balik, tetapi jika tidak demikian, ini akan menghasilkan pengecualian jika status database baru tidak kompatibel atau dapat menyebabkan kerusakan data jika operasi tidak bergantung pada status tertentu, misalnya saat menyisipkan baris baru dengan nilai kunci yang dihasilkan secara otomatis.

Ada beberapa cara untuk menangani hal ini.

Opsi 1 - Jangan lakukan apa-apa (hampir tidak ada)

Kemungkinan kegagalan koneksi selama penerapan transaksi rendah sehingga mungkin dapat diterima bagi aplikasi Anda untuk hanya gagal jika kondisi ini benar-benar terjadi.

Namun, Anda perlu menghindari penggunaan kunci yang dihasilkan oleh sistem untuk memastikan bahwa terjadi pengecualian alih-alih menambahkan baris duplikat. Pertimbangkan untuk menggunakan nilai GUID yang dihasilkan klien atau generator nilai sisi klien.

Opsi 2 - Membangun kembali status aplikasi

  1. Buang DbContextyang ada.
  2. Buat DbContext baru dan pulihkan status aplikasi Anda dari database.
  3. Beri tahu pengguna bahwa operasi terakhir mungkin belum berhasil diselesaikan.

Opsi 3 - Tambahkan verifikasi status

Untuk sebagian besar operasi yang mengubah status database dimungkinkan untuk menambahkan kode yang memeriksa apakah operasi berhasil. EF menyediakan metode ekstensi untuk mempermudah ini - IExecutionStrategy.ExecuteInTransaction.

Metode ini memulai dan melakukan transaksi dan juga menerima fungsi dalam parameter verifySucceeded yang dipanggil ketika kesalahan sementara terjadi selama penerapan transaksi.


using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();

var blogToAdd = new Blog { Url = "http://blogs.msdn.com/dotnet" };
db.Blogs.Add(blogToAdd);

await strategy.ExecuteInTransactionAsync(
    db,
    operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
    verifySucceeded: (context, cancellationToken) => context.Blogs.AsNoTracking().AnyAsync(b => b.BlogId == blogToAdd.BlogId, cancellationToken));

db.ChangeTracker.AcceptAllChanges();

Nota

Di sini SaveChanges dipanggil dengan acceptAllChangesOnSuccess diatur ke false untuk menghindari perubahan status entitas Blog menjadi Unchanged jika SaveChanges berhasil. Ini memungkinkan untuk mencoba kembali operasi yang sama jika penerapan gagal dan transaksi digulung balik.

Opsi 4 - Melacak transaksi secara manual

Jika Anda perlu menggunakan kunci yang dihasilkan secara otomatis oleh sistem atau memerlukan cara umum untuk menangani kegagalan komit yang tidak bergantung pada operasi yang dilakukan, setiap transaksi dapat ditetapkan ID yang diperiksa saat komit gagal.

  1. Tambahkan tabel ke database yang digunakan untuk melacak status transaksi.
  2. Sisipkan baris ke dalam tabel di awal setiap transaksi.
  3. Jika koneksi gagal selama komit, periksa keberadaan baris yang bersangkutan dalam database.
  4. Jika penerapan berhasil, hapus baris yang sesuai untuk menghindari pertumbuhan tabel.

using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();

db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });

var transaction = new TransactionRow { Id = Guid.NewGuid() };
db.Transactions.Add(transaction);

await strategy.ExecuteInTransactionAsync(
    db,
    operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
    verifySucceeded: (context, cancellationToken) => context.Transactions.AsNoTracking().AnyAsync(t => t.Id == transaction.Id, cancellationToken));

db.ChangeTracker.AcceptAllChanges();
db.Transactions.Remove(transaction);
await db.SaveChangesAsync();

Nota

Pastikan bahwa konteks yang digunakan untuk verifikasi memiliki strategi eksekusi yang didefinisikan, karena koneksi kemungkinan akan gagal lagi selama verifikasi jika gagal selama komitmen transaksi.

Sumber daya tambahan