Bagikan melalui


Integrasi System.Transactions dengan SQL Server

.NET Framework versi 2.0 memperkenalkan kerangka kerja transaksi yang dapat diakses melalui System.Transactions namespace. Kerangka kerja ini memaparkan transaksi dengan cara yang sepenuhnya terintegrasi dalam .NET Framework, 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.

Mulai dari ADO.NET 2.0, System.Data.SqlClient mendukung transaksi yang dapat dipromosikan 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.

Transaksi yang dapat dipromosikan hanya tersedia saat Anda menggunakan Penyedia Data .NET Framework untuk SQL Server (SqlClient) dengan SQL Server.

Membuat Transaksi yang Dapat Dipromosikan

Penyedia .NET Framework untuk SQL Server menyediakan dukungan untuk transaksi yang dapat dipromosikan, yang ditangani melalui kelas di namespace System.Transactions .NET Framework. 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, System.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 System.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 membuat blok TransactionScope dengan pernyataan using dan membuka koneksi pertama, yang secara otomatis memasukkannya ke dalam 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. Metode Complete dipanggil, yang melakukan transaksi hanya jika tidak ada pengecualian yang ditampilkan. 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.

// This function takes arguments for the 2 connection strings and commands in order  
// to create a transaction involving two SQL Servers. It returns a value > 0 if the  
// transaction committed, 0 if the transaction rolled back. To test this code, you can
// connect to two different databases on the same server by altering the connection string,  
// or to another RDBMS such as Oracle by altering the code in the connection2 code block.  
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;  
}  
' This function takes arguments for the 2 connection strings and commands in order  
' to create a transaction involving two SQL Servers. It returns a value > 0 if the  
' transaction committed, 0 if the transaction rolled back. To test this code, you can
' connect to two different databases on the same server by altering the connection string,  
' or to another RDBMS such as Oracle by altering the code in the connection2 code block.  
Public Function CreateTransactionScope( _  
  ByVal connectString1 As String, ByVal connectString2 As String, _  
  ByVal commandText1 As String, ByVal commandText2 As String) As Integer  
  
    ' Initialize the return value to zero and create a StringWriter to display results.  
    Dim returnValue As Integer = 0  
    Dim writer As System.IO.StringWriter = 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 scope As New TransactionScope()  
        Using connection1 As 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.  
                Dim command1 As SqlCommand = 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 connection2 As 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  
                        Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)  
                        returnValue = command2.ExecuteNonQuery()  
                        writer.WriteLine("Rows to be affected by command2: {0}", returnValue)  
  
                    Catch ex As Exception  
                        ' Display information that command2 failed.  
                        writer.WriteLine("returnValue for command2: {0}", returnValue)  
                        writer.WriteLine("Exception Message2: {0}", ex.Message)  
                    End Try  
                End Using  
  
            Catch ex As Exception  
                ' Display information that command1 failed.  
                writer.WriteLine("returnValue for command1: {0}", returnValue)  
                writer.WriteLine("Exception Message1: {0}", ex.Message)  
            End Try  
        End Using  
  
        ' If an exception has been thrown, Complete will
        ' not be called and the transaction is rolled back.  
        scope.Complete()  
    End Using  
  
    ' The returnValue is greater than 0 if the transaction committed.  
    If returnValue > 0 Then  
        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.")  
     End If  
  
    ' Display messages.  
    Console.WriteLine(writer.ToString())  
  
    Return returnValue  
End Function  

Lihat juga