Bagikan melalui


Menggunakan Transaksi

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

Tip

Anda dapat melihat contoh artikel ini di GitHub.

Perilaku transaksi default

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 DbContext.Database API untuk memulai, menerapkan, dan membatalkan transaksi. Contoh berikut menunjukkan dua SaveChanges operasi dan kueri LINQ yang dijalankan dalam satu transaksi:

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

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

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

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

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

Meskipun semua penyedia database relasional mendukung transaksi, jenis penyedia lain dapat melempar atau tidak ada operasi ketika API transaksi dipanggil.

Catatan

Mengontrol transaksi secara manual dengan cara ini tidak kompatibel dengan strategi eksekusi coba lagi yang dipanggil secara implisit. Lihat ketahanan Koneksi ion 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 digulung balik, jika terjadi kesalahan atau karena alasan lain. Jika SaveChanges mengalami kesalahan, secara otomatis menggulung transaksi 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 masalah konkurensi optimis terjadi.

Peringatan

Titik penyimpanan 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 mengembalikannya saat gagal:

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

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

    transaction.CreateSavepoint("BeforeMoreBlogs");

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

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

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

Transaksi lintas konteks

Anda juga dapat berbagi transaksi di beberapa instans 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 dan DbConnectionDbTransaction.

Izinkan koneksi disediakan secara eksternal

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

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

Tip

DbContextOptionsBuilder adalah API yang Anda gunakan 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 DbContext.Database.UseTransaction(DbTransaction) API 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);
using var transaction = context1.Database.BeginTransaction();
try
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    context1.SaveChanges();

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

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

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

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

Menggunakan DbTransactions eksternal (hanya 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);
connection.Open();

using var transaction = connection.BeginTransaction();
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))
    {
        context.Database.UseTransaction(transaction);
        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        context.SaveChanges();
    }

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

Menggunakan System.Transactions

Dimungkinkan untuk menggunakan transaksi sekitar jika Anda perlu berkoordinasi di seluruh cakupan yang lebih besar.

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

    try
    {
        // Run raw ADO.NET command in the transaction
        var command = connection.CreateCommand();
        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))
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
        }

        // 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))
        {
            context.Database.OpenConnection();
            context.Database.EnlistTransaction(transaction);

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

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

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

Catatan

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

Untuk informasi selengkapnya tentang TransactionScope transaksi sekitar dan , lihat dokumentasi ini.

Batasan System.Transactions

  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 bahwa API bersifat benar dengan penyedia Anda sebelum Anda mengandalkannya untuk mengelola transaksi. Anda dianjurkan untuk menghubungi penjaga penyedia database jika tidak.

  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.