Bagikan melalui


Menggunakan Transaksi

Transaksi memungkinkan beberapa operasi database diproses secara atomik. Jika transaksi dilakukan, semua operasi berhasil diterapkan ke database. Jika transaksi dibatalkan, tidak ada operasi yang diterapkan ke database.

Petunjuk / Saran

Anda dapat melihat contoh artikel ini di GitHub.

Perilaku transaksi bawaan

Secara default, jika penyedia database mendukung transaksi, semua perubahan dalam satu panggilan ke SaveChanges diterapkan dalam transaksi. Jika salah satu perubahan gagal, maka transaksi digulung balik dan tidak ada perubahan yang diterapkan ke database. Ini berarti bahwa SaveChanges dijamin berhasil sepenuhnya, atau membiarkan database tidak dimodifikasi jika terjadi kesalahan.

Untuk sebagian besar aplikasi, perilaku default ini cukup. Anda hanya boleh mengontrol transaksi secara manual jika persyaratan aplikasi Anda dianggap perlu.

Mengontrol transaksi

Anda dapat menggunakan API DbContext.Database untuk memulai, menerapkan, dan memutar kembali transaksi. Contoh berikut menunjukkan dua operasi SaveChanges dan kueri LINQ yang dijalankan dalam satu transaksi:

using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();

try
{
    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();

    var blogs = await context.Blogs
        .OrderBy(b => b.Url)
        .ToListAsync();

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

Meskipun semua penyedia database relasional mendukung transaksi, jenis penyedia lain dapat melempar atau no-op ketika API transaksi dipanggil.

Nota

Mengontrol transaksi secara manual dengan cara ini tidak sesuai dengan strategi eksekusi ulang yang dipanggil secara otomatis. Lihat Ketahanan Koneksi untuk informasi selengkapnya.

Titik simpan

Ketika SaveChanges dipanggil dan transaksi sudah berlangsung pada konteks, EF secara otomatis membuat titik penyimpanan sebelum menyimpan data apa pun. Titik penyimpanan adalah titik dalam transaksi database yang nantinya dapat dikembalikan ke kondisi semula, jika terjadi kesalahan atau karena alasan lain. Jika SaveChanges mengalami kesalahan, transaksi secara otomatis akan kembali ke titik penyimpanan, meninggalkan transaksi dalam keadaan yang sama seolah-olah tidak pernah dimulai. Ini memungkinkan Anda untuk mungkin memperbaiki masalah dan mencoba menyimpan kembali, khususnya ketika konkurensi optimis masalah terjadi.

Peringatan

Savepoint tidak kompatibel dengan Beberapa Set Hasil Aktif (MARS) SQL Server. Titik penyimpanan tidak akan dibuat oleh EF ketika MARS diaktifkan pada koneksi, bahkan jika MARS tidak aktif digunakan. Jika terjadi kesalahan selama SaveChanges, transaksi mungkin dibiarkan dalam keadaan tidak diketahui.

Anda juga dapat mengelola titik penyimpanan secara manual, seperti halnya dengan transaksi. Contoh berikut membuat titik penyimpanan dalam transaksi, dan memulihkannya ketika terjadi kegagalan:

using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();

try
{
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
    await context.SaveChangesAsync();

    await transaction.CreateSavepointAsync("BeforeMoreBlogs");

    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
    await context.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch (Exception)
{
    // If a failure occurred, we rollback to the savepoint and can continue the transaction
    await transaction.RollbackToSavepointAsync("BeforeMoreBlogs");

    // TODO: Handle failure, possibly retry inserting blogs
}

Transaksi lintas konteks

Anda juga dapat berbagi transaksi di berbagai instansi konteks. Fungsionalitas ini hanya tersedia saat menggunakan penyedia database relasional karena memerlukan penggunaan DbTransaction dan DbConnection, yang khusus untuk database relasional.

Untuk berbagi transaksi, konteks harus berbagi baik DbConnection maupun DbTransaction.

Izinkan koneksi disediakan secara eksternal

Berbagi DbConnection memerlukan kemampuan untuk meneruskan koneksi ke dalam konteks saat membangunnya.

Cara term mudah untuk memungkinkan DbConnection disediakan secara eksternal, adalah berhenti menggunakan metode DbContext.OnConfiguring untuk mengonfigurasi konteks dan membuat DbContextOptions secara eksternal dan meneruskannya ke konstruktor konteks.

Petunjuk / Saran

DbContextOptionsBuilder adalah API yang Anda gunakan di DbContext.OnConfiguring untuk mengonfigurasi konteks, Anda sekarang akan menggunakannya secara eksternal untuk membuat DbContextOptions.

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

    public DbSet<Blog> Blogs { get; set; }
}

Alternatifnya adalah tetap menggunakan DbContext.OnConfiguring, tetapi menerima DbConnection yang disimpan dan kemudian digunakan dalam DbContext.OnConfiguring.

public class BloggingContext : DbContext
{
    private DbConnection _connection;

    public BloggingContext(DbConnection connection)
    {
      _connection = connection;
    }

    public DbSet<Blog> Blogs { get; set; }

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

Berbagi koneksi dan transaksi

Anda sekarang dapat membuat beberapa instans konteks yang berbagi koneksi yang sama. Kemudian gunakan API DbContext.Database.UseTransaction(DbTransaction) untuk mendaftarkan kedua konteks dalam transaksi yang sama.

using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseSqlServer(connection)
    .Options;

using var context1 = new BloggingContext(options);
await using var transaction = await context1.Database.BeginTransactionAsync();
try
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    await context1.SaveChangesAsync();

    using (var context2 = new BloggingContext(options))
    {
        await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());

        var blogs = await context2.Blogs
            .OrderBy(b => b.Url)
            .ToListAsync();

        context2.Blogs.Add(new Blog { Url = "http://dot.net" });
        await context2.SaveChangesAsync();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

Menggunakan transaksi database eksternal (hanya untuk database relasional)

Jika Anda menggunakan beberapa teknologi akses data untuk mengakses database relasional, Anda mungkin ingin berbagi transaksi antara operasi yang dilakukan oleh berbagai teknologi ini.

Contoh berikut, menunjukkan cara melakukan operasi SqlClient ADO.NET dan operasi Entity Framework Core dalam transaksi yang sama.

using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();

await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
    // Run raw ADO.NET command in the transaction
    var command = connection.CreateCommand();
    command.Transaction = transaction;
    command.CommandText = "DELETE FROM dbo.Blogs";
    command.ExecuteNonQuery();

    // Run an EF Core command in the transaction
    var options = new DbContextOptionsBuilder<BloggingContext>()
        .UseSqlServer(connection)
        .Options;

    using (var context = new BloggingContext(options))
    {
        await context.Database.UseTransactionAsync(transaction);
        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        await context.SaveChangesAsync();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

Menggunakan System.Transactions

Dimungkinkan untuk menggunakan transaksi lingkungan jika Anda perlu berkoordinasi dalam cakupan yang lebih luas.

using (var scope = new TransactionScope(
           TransactionScopeOption.Required,
           new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();

    try
    {
        // Run raw ADO.NET command in the transaction
        var command = connection.CreateCommand();
        command.CommandText = "DELETE FROM dbo.Blogs";
        await command.ExecuteNonQueryAsync();

        // Run an EF Core command in the transaction
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;

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

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        scope.Complete();
    }
    catch (Exception)
    {
        // TODO: Handle failure
    }
}

Dimungkinkan juga untuk mendaftar dalam transaksi eksplisit.

using (var transaction = new CommittableTransaction(
           new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    var connection = new SqlConnection(connectionString);

    try
    {
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;

        using (var context = new BloggingContext(options))
        {
            await context.Database.OpenConnectionAsync();
            context.Database.EnlistTransaction(transaction);

            // Run raw ADO.NET command in the transaction
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            await command.ExecuteNonQueryAsync();

            // Run an EF Core command in the transaction
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            await context.SaveChangesAsync();
            await context.Database.CloseConnectionAsync();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        transaction.Commit();
    }
    catch (Exception)
    {
        // TODO: Handle failure
    }
}

Nota

Jika Anda menggunakan API asinkron, pastikan untuk menentukan TransactionScopeAsyncFlowOption.Enabled di konstruktor TransactionScope untuk memastikan bahwa transaksi lingkungan mengalir dalam seluruh panggilan asinkron.

Untuk informasi lebih lanjut tentang TransactionScope dan transaksi ambient, silakan lihat dokumentasi ini.

Pembatasan Transaksi Sistem

  1. EF Core mengandalkan penyedia database untuk menerapkan dukungan untuk System.Transactions. Jika penyedia tidak menerapkan dukungan untuk System.Transactions, ada kemungkinan bahwa panggilan ke API ini akan sepenuhnya diabaikan. SqlClient mendukungnya.

    Penting

    Disarankan agar Anda menguji apakah API berfungsi dengan benar dengan penyedia Anda sebelum Anda mengandalkannya untuk mengelola transaksi. Anda dianjurkan untuk menghubungi pengelola penyedia basis data jika hal tersebut tidak terjadi.

  2. Dukungan transaksi terdistribusi di System.Transactions ditambahkan ke .NET 7.0 hanya untuk Windows. Setiap upaya untuk menggunakan transaksi terdistribusi pada versi .NET yang lebih lama atau pada platform non-Windows akan gagal.

  3. TransactionScope tidak mendukung penerapan/pembatalan asinkron; itu berarti bahwa membuangnya secara sinkron memblokir utas yang dieksekusi hingga operasi selesai.