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 = 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 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 = 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 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 DbConnection
DbTransaction
.
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 = 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 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);
await connection.OpenAsync();
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 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);
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
}
}
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
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.
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.
TransactionScope tidak mendukung penerapan/pembatalan asinkron; itu berarti bahwa membuangnya secara sinkron memblokir utas yang dieksekusi hingga operasi selesai.