Bekerja dengan Transaksi
Catatan
Hanya EF6 dan seterusnya - Fitur, API, dll. yang dibahas di halaman ini dimasukkan dalam Entity Framework 6. Jika Anda menggunakan versi yang lebih lama, beberapa atau semua informasi tidak berlaku.
Dokumen ini akan menjelaskan penggunaan transaksi di EF6 termasuk penyempurnaan yang telah kami tambahkan sejak EF5 untuk memudahkan bekerja dengan transaksi.
Apa yang dilakukan EF secara default
Di semua versi Entity Framework, setiap kali Anda menjalankan SaveChanges() untuk menyisipkan, memperbarui, atau menghapus pada database, kerangka kerja akan membungkus operasi tersebut dalam transaksi. Transaksi ini hanya berlangsung cukup lama untuk menjalankan operasi dan kemudian selesai. Ketika Anda menjalankan operasi lain seperti itu, transaksi baru dimulai.
Dimulai dengan EF6 Database.ExecuteSqlCommand() secara default akan membungkus perintah dalam transaksi jika belum ada. Ada kelebihan beban metode ini yang memungkinkan Anda untuk mengambil alih perilaku ini jika Anda mau. Juga dalam eksekusi EF6 prosedur tersimpan yang disertakan dalam model melalui API seperti ObjectContext.ExecuteFunction() melakukan hal yang sama (kecuali bahwa perilaku default tidak dapat ditimpa saat ini).
Dalam kedua kasus, tingkat isolasi transaksi adalah tingkat isolasi apa pun yang penyedia database pertimbangkan pengaturan defaultnya. Secara default, misalnya, di SQL Server, ini adalah READ COMMITTED.
Kerangka Kerja Entitas tidak membungkus kueri dalam transaksi.
Fungsionalitas default ini cocok untuk banyak pengguna dan jika demikian tidak perlu melakukan sesuatu yang berbeda di EF6; hanya menulis kode seperti yang selalu Anda lakukan.
Namun beberapa pengguna memerlukan kontrol yang lebih besar atas transaksi mereka - ini tercakup dalam bagian berikut.
Cara kerja API
Sebelum Kerangka Kerja Entitas EF6 bersikeras membuka koneksi database itu sendiri (itu melemparkan pengecualian jika melewati koneksi yang sudah terbuka). Karena transaksi hanya dapat dimulai pada koneksi terbuka, ini berarti bahwa satu-satunya cara pengguna dapat membungkus beberapa operasi ke dalam satu transaksi adalah dengan menggunakan TransactionScope atau menggunakan ObjectContext.Koneksiproperti ion dan mulai memanggil Open() dan BeginTransaction() langsung pada objek Entitas Koneksi ion yang dikembalikan. Selain itu, panggilan API yang menghubungi database akan gagal jika Anda telah memulai transaksi pada koneksi database yang mendasar sendiri.
Catatan
Batasan hanya menerima koneksi tertutup dihapus dalam Kerangka Kerja Entitas 6. Untuk detailnya, lihat Manajemen Koneksi ion.
Dimulai dengan EF6, kerangka kerja sekarang menyediakan:
- Database.BeginTransaction() : Metode yang lebih mudah bagi pengguna untuk memulai dan menyelesaikan transaksi sendiri dalam DbContext yang ada – memungkinkan beberapa operasi digabungkan dalam transaksi yang sama dan karenanya semua berkomitmen atau semua digulung balik sebagai satu. Ini juga memungkinkan pengguna untuk lebih mudah menentukan tingkat isolasi untuk transaksi.
- Database.UseTransaction() : yang memungkinkan DbContext menggunakan transaksi yang dimulai di luar Kerangka Kerja Entitas.
Menggabungkan beberapa operasi ke dalam satu transaksi dalam konteks yang sama
Database.BeginTransaction() memiliki dua penimpaan – yang mengambil IsolationLevel eksplisit dan yang tidak mengambil argumen dan menggunakan IsolationLevel default dari penyedia database yang mendasar. Kedua penimpaan mengembalikan objek DbContextTransaction yang menyediakan metode Commit() dan Rollback() yang melakukan penerapan dan pembatalan pada transaksi penyimpanan yang mendasar.
DbContextTransaction dimaksudkan untuk dibuang setelah diterapkan atau digulung balik. Salah satu cara mudah untuk mencapai hal ini adalah sintaksis using(...) {...} yang akan secara otomatis memanggil Dispose() ketika blok penggunaan selesai:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void StartOwnTransactionWithinContext()
{
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
context.Database.ExecuteSqlCommand(
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
dbContextTransaction.Commit();
}
}
}
}
}
Catatan
Memulai transaksi mengharuskan koneksi penyimpanan yang mendasar terbuka. Jadi memanggil Database.BeginTransaction() akan membuka koneksi jika belum dibuka. Jika DbContextTransaction membuka koneksi, maka DbContextTransaction akan menutupnya ketika Dispose() dipanggil.
Meneruskan transaksi yang ada ke konteks
Terkadang Anda menginginkan transaksi yang bahkan lebih luas dalam cakupan dan yang mencakup operasi pada database yang sama tetapi di luar EF sepenuhnya. Untuk mencapai hal ini, Anda harus membuka koneksi dan memulai transaksi sendiri lalu memberi tahu EF a) untuk menggunakan koneksi database yang sudah dibuka, dan b) untuk menggunakan transaksi yang ada pada koneksi tersebut.
Untuk melakukan ini, Anda harus menentukan dan menggunakan konstruktor pada kelas konteks Anda yang mewarisi dari salah satu konstruktor DbContext yang mengambil i) parameter koneksi yang ada dan ii) boolean contextOwns Koneksi ion.
Catatan
Bendera contextOwns Koneksi ion harus diatur ke false saat dipanggil dalam skenario ini. Ini penting karena menginformasikan Kerangka Kerja Entitas bahwa ia tidak boleh menutup koneksi ketika dilakukan dengannya (misalnya, lihat baris 4 di bawah):
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
}
}
Selain itu, Anda harus memulai transaksi sendiri (termasuk IsolationLevel jika Anda ingin menghindari pengaturan default) dan memberi tahu Kerangka Kerja Entitas bahwa ada transaksi yang sudah dimulai pada koneksi (lihat baris 33 di bawah).
Kemudian Anda bebas untuk menjalankan operasi database baik langsung di Sql Koneksi ion itu sendiri, atau di DbContext. Semua operasi tersebut dijalankan dalam satu transaksi. Anda bertanggung jawab untuk melakukan atau menggulung balik transaksi dan untuk memanggil Dispose() di atasnya, serta untuk menutup dan membuang koneksi database. Contohnya:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
}
}
}
}
Membersihkan transaksi
Anda dapat meneruskan null ke Database.UseTransaction() untuk menghapus pengetahuan Entity Framework tentang transaksi saat ini. Kerangka Kerja Entitas tidak akan menerapkan atau memutar kembali transaksi yang ada saat Anda melakukan ini, jadi gunakan dengan hati-hati dan hanya jika Anda yakin ini adalah apa yang ingin Anda lakukan.
Kesalahan dalam UseTransaction
Anda akan melihat pengecualian dari Database.UseTransaction() jika Anda melewati transaksi saat:
- Kerangka Kerja Entitas sudah memiliki transaksi yang ada
- Kerangka Kerja Entitas sudah beroperasi dalam TransactionScope
- Objek koneksi dalam transaksi yang dilewatkan null. Artinya, transaksi tidak terkait dengan koneksi - biasanya ini adalah tanda bahwa transaksi telah selesai
- Objek koneksi dalam transaksi yang diteruskan tidak cocok dengan koneksi Kerangka Kerja Entitas.
Menggunakan transaksi dengan fitur lain
Bagian ini merinci bagaimana transaksi di atas berinteraksi dengan:
- Ketahanan koneksi
- Metode asinkron
- Transaksi TransactionScope
Ketahanan Sambungan
Fitur ketahanan Koneksi ion baru tidak berfungsi dengan transaksi yang dimulai pengguna. Untuk detailnya, lihat Mencoba Kembali Strategi Eksekusi.
Pemrograman Asinkron
Pendekatan yang diuraikan di bagian sebelumnya tidak memerlukan opsi atau pengaturan lebih lanjut untuk bekerja dengan kueri asinkron dan metode penyimpanan. Tetapi ketahuilah bahwa, tergantung pada apa yang Anda lakukan dalam metode asinkron, ini dapat mengakibatkan transaksi yang berjalan lama - yang pada gilirannya dapat menyebabkan kebuntuan atau pemblokiran yang buruk untuk performa aplikasi keseluruhan.
Transaksi TransactionScope
Sebelum EF6, cara yang direkomendasikan untuk menyediakan transaksi cakupan yang lebih besar adalah dengan menggunakan objek TransactionScope:
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
}
scope.Complete();
}
}
}
}
Sql Koneksi ion dan Entity Framework akan menggunakan transaksi TransactionScope sekitar dan karenanya dilakukan bersama-sama.
Dimulai dengan .NET 4.5.1 TransactionScope telah diperbarui untuk juga bekerja dengan metode asinkron melalui penggunaan enumerasi TransactionScopeAsyncFlowOption :
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
public static void AsyncTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (var conn = new SqlConnection("..."))
{
await conn.OpenAsync();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
await sqlCommand.ExecuteNonQueryAsync();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
await context.SaveChangesAsync();
}
}
scope.Complete();
}
}
}
}
Masih ada beberapa batasan untuk pendekatan TransactionScope:
- Memerlukan .NET 4.5.1 atau lebih besar untuk bekerja dengan metode asinkron.
- Ini tidak dapat digunakan dalam skenario cloud kecuali Anda yakin Anda memiliki satu dan hanya satu koneksi (skenario cloud tidak mendukung transaksi terdistribusi).
- Ini tidak dapat dikombinasikan dengan pendekatan Database.UseTransaction() dari bagian sebelumnya.
- Ini akan melemparkan pengecualian jika Anda mengeluarkan DDL dan belum mengaktifkan transaksi terdistribusi melalui Layanan MSDTC.
Keuntungan dari pendekatan TransactionScope:
- Ini akan secara otomatis meningkatkan transaksi lokal ke transaksi terdistribusi jika Anda membuat lebih dari satu koneksi ke database tertentu atau menggabungkan koneksi ke satu database dengan koneksi ke database yang berbeda dalam transaksi yang sama (catatan: Anda harus memiliki layanan MSDTC yang dikonfigurasi untuk memungkinkan transaksi terdistribusi agar ini berfungsi).
- Kemudahan pengkodian. Jika Anda lebih suka transaksi menjadi sekitar dan ditangani secara implisit di latar belakang daripada secara eksplisit di bawah kontrol Anda, pendekatan TransactionScope mungkin lebih cocok untuk Anda.
Singkatnya, dengan API Database.BeginTransaction() dan Database.UseTransaction() baru di atas, pendekatan TransactionScope tidak lagi diperlukan untuk sebagian besar pengguna. Jika Anda terus menggunakan TransactionScope, maka ketahui batasan di atas. Sebaiknya gunakan pendekatan yang diuraikan di bagian sebelumnya sebagai gantinya jika memungkinkan.