Integrasi System.Transactions dengan SQL Server
Berlaku untuk: .NET Framework .NET .NET Standard
.NET menyertakan kerangka kerja transaksi yang dapat diakses melalui System.Transactions namespace layanan. Kerangka kerja ini mengekspos transaksi dengan cara yang sepenuhnya terintegrasi dalam .NET, termasuk ADO.NET.
Selain peningkatan kemampuan pemrogaman, System.Transactions dan ADO.NET dapat bekerja sama untuk mengoordinasikan pengoptimalan saat Anda bekerja dengan transaksi. Transaksi yang dapat dipromosikan adalah transaksi ringan (lokal) yang dapat dipromosikan secara otomatis menjadi transaksi terdistribusi penuh sesuai kebutuhan.
Penyedia Data Microsoft SqlClient untuk SQL Server mendukung transaksi yang dapat diprovokasi saat Anda bekerja dengan SQL Server. Transaksi yang dapat diproyeksikan tidak memanggil overhead tambahan dari transaksi terdistribusi kecuali overhead yang ditambahkan diperlukan. Transaksi yang dapat dipromosikan bersifat otomatis dan tidak memerlukan intervensi dari pengembang.
Membuat transaksi yang dapat diproyeksikan
Penyedia Data Microsoft SqlClient untuk SQL Server menyediakan dukungan untuk transaksi yang dapat diprovokasi, yang ditangani melalui kelas di System.Transactions namespace layanan. Transaksi yang dapat dipromosikan mengoptimalkan transaksi terdistribusi dengan menunda pembuatan transaksi terdistribusi hingga diperlukan. Jika hanya satu pengelola sumber daya yang diperlukan, tidak ada transaksi terdistribusi yang terjadi.
Catatan
Dalam skenario tepercaya parsial, DistributedTransactionPermission diperlukan saat transaksi dipromosikan menjadi transaksi terdistribusi.
Skenario transaksi yang dapat dipromosikan
Transaksi terdistribusi biasanya menggunakan sumber daya sistem yang signifikan, dikelola oleh Koordinator Transaksi Terdistribusi Microsoft (MS DTC), yang mengintegrasikan semua pengelola sumber daya yang diakses dalam transaksi. Transaksi yang dapat dipromosikan adalah bentuk khusus dari transaksi System.Transactions yang secara efektif mendelegasikan pekerjaan ke transaksi SQL Server sederhana. System.Transactions, Microsoft.Data.SqlClient, dan SQL Server mengoordinasikan pekerjaan yang terlibat dalam menangani transaksi, mempromosikannya ke transaksi terdistribusi penuh sesuai kebutuhan.
Manfaat menggunakan transaksi yang dapat dipromosikan adalah ketika koneksi dibuka dengan menggunakan transaksi TransactionScope aktif, dan tidak ada koneksi lain yang dibuka, transaksi tersebut dilakukan sebagai transaksi ringan, sebagai ganti menimbulkan overhead tambahan dari transaksi terdistribusi penuh.
Kata kunci string koneksi
Properti ConnectionString mendukung kata kunci, Enlist
, yang menunjukkan apakah Microsoft.Data.SqlClient akan mendeteksi konteks transaksional dan secara otomatis mendaftarkan koneksi dalam transaksi terdistribusi. Jika Enlist=true
, koneksi secara otomatis terdaftar dalam konteks transaksi saat ini dari utas pembuka. Jika Enlist=false
, koneksi SqlClient
tidak berinteraksi dengan transaksi terdistribusi. Nilai default untuk Enlist
adalah true. Jika Enlist
tidak ditentukan dalam rangkaian koneksi, koneksi secara otomatis terdaftar dalam transaksi terdistribusi jika ada yang terdeteksi saat koneksi dibuka.
Kata kunci Transaction Binding
dalam SqlConnection string koneksi mengontrol asosiasi koneksi dengan transaksi System.Transactions
yang terdaftar. Ini juga tersedia melalui properti TransactionBinding dari SqlConnectionStringBuilder.
Tabel berikut menjelaskan nilai yang mungkin.
Kata kunci | Deskripsi |
---|---|
Terikat secara implisit | Default. Koneksi terputus dari transaksi saat berakhir, beralih kembali ke mode penerapan otomatis. |
Pelonggaran Eksplisit | Koneksi tetap melekat pada transaksi sampai transaksi ditutup. Koneksi akan gagal jika transaksi terkait tidak aktif atau tidak cocok dengan Current. |
Menggunakan TransactionScope
Kelas TransactionScope membuat blok kode transaksional dengan secara implisit mendaftarkan koneksi dalam transaksi terdistribusi. Anda harus memanggil metode Complete di akhir blok TransactionScope sebelum meninggalkannya. Meninggalkan blok akan memanggil metode Dispose. Jika pengecualian ditampilkan, yang menyebabkan kode meninggalkan ruang lingkup, transaksi dianggap dibatalkan.
Kami menyarankan Anda menggunakan blok using
untuk memastikan bahwa Dispose dipanggil pada objek TransactionScope saat blok penggunaan keluar. Kegagalan untuk melakukan atau mengembalikan transaksi yang tertunda dapat secara signifikan merusak performa karena batas waktu default untuk TransactionScope adalah satu menit. Jika Anda tidak menggunakan pernyataan using
, Anda harus melakukan semua pekerjaan di blok Try
dan secara eksplisit memanggil metode Dispose di blok Finally
.
Jika pengecualian terjadi di TransactionScope, transaksi ditandai sebagai tidak konsisten dan ditinggalkan. Ini akan dibatalkan ketika TransactionScope dibuang. Jika tidak ada pengecualian terjadi, transaksi yang berpartisipasi akan diterapkan.
Catatan
Kelas TransactionScope
membuat transaksi dengan IsolationLevel dari Serializable
secara default. Bergantung pada aplikasi Anda, Anda mungkin ingin mempertimbangkan untuk menurunkan tingkat isolasi guna menghindari konflik yang tinggi dalam aplikasi Anda.
Catatan
Sebaiknya Anda hanya melakukan pembaruan, penyisipan, dan penghapusan dalam transaksi terdistribusi karena tindakan tersebut menghabiskan sumber daya database yang signifikan. Pernyataan tertentu mungkin akan mengunci sumber daya database yang tidak perlu, dan dalam beberapa skenario, Anda mungkin harus menggunakan transaksi untuk memilih. Setiap pekerjaan non-database harus dilakukan di luar lingkup transaksi, kecuali jika melibatkan pengelola sumber daya lain yang ditransaksikan. Meskipun pengecualian dalam ruang lingkup transaksi mencegah transaksi diterapkan, kelas TransactionScope tidak memiliki ketentuan untuk mengembalikan perubahan apa pun yang telah dibuat kode Anda di luar cakupan transaksi itu sendiri. Jika Anda harus mengambil tindakan saat transaksi dibatalkan, Anda harus menulis penerapan antarmuka IEnlistmentNotification Anda sendiri dan secara eksplisit mendaftar dalam transaksi.
Contoh
Bekerja dengan System.Transactions mengharuskan Anda memiliki referensi ke System.Transactions.dll.
Fungsi berikut menunjukkan cara membuat transaksi yang dapat dipromosikan pada dua instans SQL Server yang berbeda, yang diwakili oleh dua objek SqlConnection berbeda, yang dibungkus dalam blok TransactionScope.
Kode di bawah ini membuat TransactionScope blok dengan using
pernyataan dan membuka koneksi pertama, yang secara otomatis mendaftarkannya di TransactionScope.
Transaksi ini awalnya terdaftar sebagai transaksi ringan, bukan transaksi terdistribusi penuh. Koneksi kedua terdaftar di TransactionScope hanya jika perintah di koneksi pertama tidak menampilkan pengecualian. Ketika koneksi kedua dibuka, transaksi secara otomatis dipromosikan menjadi transaksi terdistribusi penuh.
Kemudian, Complete metode ini dipanggil, yang melakukan transaksi hanya jika tidak ada pengecualian yang dilemparkan. Jika pengecualian telah dilemparkan pada titik mana pun di blok TransactionScope, Complete
tidak akan dipanggil, dan transaksi terdistribusi akan dibatalkan ketika TransactionScope dibuang di akhir blok using
-nya.
using System;
using System.Transactions;
using Microsoft.Data.SqlClient;
class Program
{
static void Main(string[] args)
{
string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = AdventureWorks";
string commandText1 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
string commandText2 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";
int result = CreateTransactionScope(connectionString, connectionString, commandText1, commandText2);
Console.WriteLine("result = " + result);
}
static public int CreateTransactionScope(string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = new System.IO.StringWriter();
// Create the TransactionScope in which to execute the commands, guaranteeing
// that both commands will commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
try
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// if you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources by opening connection2
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
try
{
// The transaction is promoted to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
catch (Exception ex)
{
// Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue);
writer.WriteLine("Exception Message2: {0}", ex.Message);
}
}
catch (Exception ex)
{
// Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue);
writer.WriteLine("Exception Message1: {0}", ex.Message);
}
}
// If an exception has been thrown, Complete will not
// be called and the transaction is rolled back.
scope.Complete();
}
// The returnValue is greater than 0 if the transaction committed.
if (returnValue > 0)
{
writer.WriteLine("Transaction was committed.");
}
else
{
// You could write additional business logic here, notify the caller by
// throwing a TransactionAbortedException, or log the failure.
writer.WriteLine("Transaction rolled back.");
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
}