Menerapkan Transaksi Implisit menggunakan Cakupan Transaksi

Kelas ini TransactionScope menyediakan cara sederhana untuk menandai blok kode sebagai berpartisipasi dalam transaksi, tanpa mengharuskan Anda untuk berinteraksi dengan transaksi itu sendiri. Cakupan transaksi dapat memilih dan mengelola transaksi sekitar secara otomatis. Karena kemudahan penggunaan dan efisiensinya, disarankan agar Anda menggunakan TransactionScope kelas saat mengembangkan aplikasi transaksi.

Selain itu, Anda tidak perlu mendaftarkan sumber daya secara eksplisit dalam transaksi. Setiap System.Transactions manajer sumber daya (seperti SQL Server 2005) dapat mendeteksi keberadaan transaksi ambien yang dibuat oleh ruang lingkup dan bergabung secara otomatis.

Membuat ruang lingkup transaksi

Sampel berikut menunjukkan penggunaan TransactionScope kelas sederhana.

// This function takes arguments for 2 connection strings and commands to create a transaction
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
// transaction is 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 3rd party RDBMS 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();

    try
    {
        // Create the TransactionScope to execute the commands, guaranteeing
        // that both commands can commit or roll back as a single unit of work.
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                // 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 as connection2 is opened
                // only when there is a chance that the transaction can commit.
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    // The transaction is escalated 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);
                }
            }

            // The Complete method commits the transaction. If an exception has been thrown,
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();
        }
    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
    }

    // Display messages.
    Console.WriteLine(writer.ToString());

    return returnValue;
}
'  This function takes arguments for 2 connection strings and commands to create a transaction
'  involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
'  transaction is 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 3rd party RDBMS
'  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

    Try
        ' Create the TransactionScope to execute the commands, guaranteeing
        '  that both commands can commit or roll back as a single unit of work.
        Using scope As New TransactionScope()
            Using connection1 As New SqlConnection(connectString1)
                ' 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 as connection2 is opened
                ' only when there is a chance that the transaction can commit.
                Using connection2 As New SqlConnection(connectString2)
                    ' The transaction is escalated 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)
                End Using
            End Using

            ' The Complete method commits the transaction. If an exception has been thrown,
            ' Complete is called and the transaction is rolled back.
            scope.Complete()
        End Using
    Catch ex As TransactionAbortedException
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
    End Try

    ' Display messages.
    Console.WriteLine(writer.ToString())

    Return returnValue
End Function

Cakupan transaksi dimulai setelah Anda membuat objek baru TransactionScope . Seperti yang diilustrasikan dalam sampel kode, disarankan agar Anda menetapkan lingkup melalui pernyataan using. Pernyataan using ini tersedia baik di C# maupun di Visual Basic, dan berfungsi seperti blok try...finally untuk memastikan bahwa lingkup dibuang dengan benar.

Ketika Anda menginstansiasi TransactionScope, manajer transaksi menentukan transaksi mana yang akan diikuti. Setelah ditentukan, cakupan selalu berpartisipasi dalam transaksi tersebut. Keputusan didasarkan pada dua faktor: apakah transaksi lingkungan ada dan parameter TransactionScopeOption bernilai dalam konstruktor. Transaksi sekitar adalah transaksi tempat kode Anda dijalankan. Anda dapat memperoleh referensi ke transaksi ambient dengan memanggil properti statis Transaction.Current dari kelas Transaction. Untuk informasi selengkapnya tentang bagaimana parameter ini digunakan, lihat bagian Mengelola alur transaksi menggunakan TransactionScopeOption dari topik ini.

Menyelesaikan lingkup transaksi

Ketika aplikasi Anda menyelesaikan semua pekerjaan yang ingin dilakukannya dalam transaksi, Anda harus memanggil TransactionScope.Complete metode hanya sekali untuk memberi tahu manajer transaksi bahwa itu dapat diterima untuk melakukan transaksi. Ini adalah praktik yang sangat baik untuk menempatkan panggilan Complete sebagai pernyataan terakhir dalam blok using.

Gagal memanggil metode ini membatalkan transaksi, karena manajer transaksi menafsirkan ini sebagai kegagalan sistem, atau setara dengan pengecualian yang dilemparkan dalam cakupan transaksi. Namun, memanggil metode ini tidak menjamin bahwa transaksi akan dilakukan. Ini hanyalah cara untuk menginformasikan kepada manajer transaksi tentang status Anda. Setelah memanggil metode Complete, Anda tidak dapat lagi mengakses transaksi ambien dengan menggunakan properti Current, dan mencoba melakukannya akan mengakibatkan pengecualian.

Jika objek TransactionScope membuat transaksi pada awalnya, pekerjaan aktual untuk mengesahkan transaksi oleh manajer transaksi dilakukan setelah baris kode terakhir di blok using. Jika tidak membuat transaksi, penerapan terjadi setiap kali Commit dipanggil oleh pemilik CommittableTransaction objek. Pada saat itu manajer transaksi memanggil manajer sumber daya dan memberi tahu mereka untuk mengkomit atau membatalkan, berdasarkan apakah metode Complete dipanggil pada objek TransactionScope.

Pernyataan using memastikan bahwa metode Dispose dari objek TransactionScope dipanggil bahkan jika terjadi suatu pengecualian. Metode Dispose menandai akhir cakupan transaksi. Pengecualian yang terjadi setelah memanggil metode ini mungkin tidak memengaruhi transaksi. Metode ini juga memulihkan transaksi sekitar ke status sebelumnya.

TransactionAbortedException akan dilemparkan jika cakupan membuat transaksi, dan transaksi tersebut dibatalkan. TransactionInDoubtException dilemparkan jika manajer transaksi tidak dapat mencapai keputusan Commit. Tidak ada pengecualian yang dilemparkan jika transaksi dilakukan.

Mengembalikan transaksi

Jika Anda ingin memutar kembali transaksi, Anda tidak boleh memanggil Complete metode dalam cakupan transaksi. Misalnya, Anda dapat melemparkan pengecualian dalam cakupan. Transaksi tempat ia berpartisipasi akan digulung balik.

Mengelola alur transaksi menggunakan TransactionScopeOption

Cakupan transaksi dapat ditumpuk dengan memanggil metode yang menggunakan TransactionScope dari dalam metode yang menggunakan cakupannya sendiri, seperti halnya dengan RootMethod metode dalam contoh berikut,

void RootMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        SomeMethod();
        scope.Complete();
    }
}

void SomeMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        scope.Complete();
    }
}

Cakupan transaksi terbanyak disebut sebagai cakupan akar.

Kelas TransactionScope menyediakan beberapa konstruktor yang memiliki kelebihan beban dan menerima enumerasi dari jenis TransactionScopeOption, yang menentukan perilaku transaksional dari cakupan.

Objek TransactionScope memiliki tiga opsi:

  • Bergabunglah dengan transaksi sekitar, atau buat transaksi baru jika tidak ada.

  • Menjadi ruang lingkup akar yang baru, yaitu, memulai transaksi baru dan menjadikan transaksi tersebut sebagai transaksi latar baru di dalam ruang lingkupnya sendiri.

  • Tidak mengambil bagian dalam transaksi sama sekali. Akibatnya, tidak ada transaksi latar.

Jika cakupan diinstansiasi dengan Required, dan terdapat transaksi lingkungan, cakupan tersebut bergabung dengan transaksi itu. Jika, di sisi lain, tidak ada transaksi lingkungan, maka ruang lingkup membuat transaksi baru, dan menjadi ruang lingkup akar. Ini adalah nilai default. Ketika Required digunakan, kode di dalam cakupan tidak perlu berperilaku berbeda, baik itu berada di tingkat akar maupun bergabung dengan transaksi yang sedang berlangsung. Ini harus beroperasi secara identik dalam kedua kasus.

Jika cakupan dibuat dengan RequiresNew, cakupannya selalu menjadi cakupan akar. Ini memulai transaksi baru, dan transaksinya menjadi transaksi lingkungan baru di dalam cakupan.

Jika cakupan dibuat dengan Suppress, tidak akan terlibat dalam transaksi, terlepas dari apakah ada transaksi yang sedang berlangsung. Ruang lingkup yang dibuat dengan nilai ini selalu memiliki null sebagai transaksi lingkungannya.

Opsi di atas dirangkum dalam tabel berikut.

TransactionScopeOption Transaksi Lingkungan Otomatis Ruang lingkup berperan dalam
Diperlukan Tidak. Transaksi Baru (akan menjadi dasar)
Memerlukan Baru Tidak. Transaksi Baru (akan menjadi dasar)
Menahan Tidak. Tidak Ada Transaksi
Diperlukan Ya Transaksi Lingkungan Otomatis
Memerlukan Baru Ya Transaksi Baru (akan menjadi dasar)
Menahan Ya Tidak Ada Transaksi

Ketika objek bergabung dengan transaksi ambient yang ada, menghapuskan objek cakupan mungkin tidak mengakhiri transaksi, kecuali cakupan membatalkan transaksi. Jika transaksi lingkungan dibuat oleh cakupan akar, hanya ketika cakupan akar telah dihentikan, baru kemudian Commit dipanggil pada transaksi. Jika transaksi dibuat secara manual, transaksi berakhir ketika dibatalkan, atau dilakukan oleh pembuatnya.

Contoh berikut menunjukkan sebuah TransactionScope objek yang menghasilkan tiga objek cakupan bersarang, masing-masing diinstansiasi dengan nilai TransactionScopeOption yang berbeda.

using(TransactionScope scope1 = new TransactionScope())
//Default is Required
{
    using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        //...
    }

    using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        //...
    }

    using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        //...
    }
}

Contoh menunjukkan blok kode tanpa transaksi lingkungan yang membuat lingkup baru (scope1) dengan Required. Cakupan scope1 adalah cakupan akar karena membuat transaksi baru (Transaksi A) dan menjadikan Transaksi A sebagai transaksi lingkungan. Scope1 kemudian membuat tiga objek lagi, masing-masing dengan nilai yang berbeda TransactionScopeOption . Misalnya, scope2 dibuat dengan Required, dan karena ada transaksi sekitar, transaksi tersebut bergabung dengan transaksi pertama yang dibuat oleh scope1. Perhatikan bahwa scope3 adalah cakupan akar dari transaksi baru, dan scope4 tidak memiliki transaksi lingkungan.

Meskipun nilai TransactionScopeOption default dan yang paling umum digunakan adalah Required, masing-masing nilai lainnya memiliki tujuan uniknya.

Kode non-transaksi di dalam lingkup transaksi

Suppress berguna ketika Anda ingin mempertahankan operasi yang dilakukan oleh bagian kode, dan tidak ingin membatalkan transaksi sekitar jika operasi gagal. Misalnya, ketika Anda ingin melakukan operasi pencatatan atau audit, atau ketika Anda ingin menerbitkan acara kepada pelanggan terlepas dari apakah transaksi yang sedang berlangsung Anda berhasil atau dibatalkan. Nilai ini memungkinkan Anda memiliki bagian kode non-transaksi di dalam cakupan transaksi, seperti yang ditunjukkan dalam contoh berikut.

using(TransactionScope scope1 = new TransactionScope())
{
    try
    {
        //Start of non-transactional section
        using(TransactionScope scope2 = new
            TransactionScope(TransactionScopeOption.Suppress))
        {
            //Do non-transactional work here
        }
        //Restores ambient transaction here
   }
   catch {}
   //Rest of scope1
}

Pemungutan suara di dalam ruang lingkup bertingkat

Meskipun cakupan berlapis dapat bergabung dengan transaksi sekitar cakupan akar, panggilan Complete dalam cakupan berlapis tidak berpengaruh pada cakupan akar. Transaksi hanya akan dilakukan jika semua cakupan, dari cakupan akar hingga cakupan berlapis terakhir, pilih untuk melakukan transaksi. Tidak memanggil Complete dalam cakupan berlapis akan memengaruhi cakupan akar, karena transaksi sekitar akan segera dibatalkan.

Mengatur waktu habis untuk TransactionScope

Beberapa konstruktor TransactionScope yang kelebihan beban menerima nilai jenis TimeSpan, yang digunakan untuk mengontrol batas waktu transaksi. Batas waktu yang diatur ke nol berarti batas waktu tak terbatas. Waktu tunggu tak terbatas sangat berguna terutama untuk penelusuran kesalahan, ketika Anda ingin mengisolasi masalah dalam logika bisnis dengan menggali kode Anda lebih dalam, dan Anda tidak ingin transaksi yang Anda debug berakhir saat Anda sedang berusaha menemukan masalah tersebut. Berhati-hatilah menggunakan nilai batas waktu tak terbatas dalam semua kasus lain, karena mengambil alih perlindungan terhadap kebuntuan transaksi.

Anda biasanya mengatur batas TransactionScope waktu ke nilai selain default dalam dua kasus. Yang pertama adalah selama pengembangan, ketika Anda ingin menguji cara aplikasi Anda menangani transaksi yang dibatalkan. Dengan mengatur batas waktu ke nilai kecil (seperti satu milidetik), Anda menyebabkan transaksi Anda gagal dan dengan demikian dapat mengamati kode penanganan kesalahan Anda. Kasus kedua di mana Anda menetapkan nilai menjadi kurang dari batas waktu default adalah ketika Anda yakin bahwa lingkup terlibat dalam perebutan sumber daya, mengakibatkan kebuntuan. Dalam hal ini, Anda ingin membatalkan transaksi sesegera mungkin dan tidak menunggu batas waktu default kedaluwarsa.

Ketika cakupan bergabung dengan transaksi lingkungan tetapi menentukan batas waktu yang lebih kecil daripada yang diatur pada transaksi lingkungan, batas waktu baru yang lebih singkat diberlakukan pada objek TransactionScope, dan cakupan harus berakhir dalam batas waktu yang lebih singkat yang telah ditentukan, atau transaksi secara otomatis dibatalkan. Jika batas waktu cakupan berlapis lebih dari transaksi sekitar, itu tidak berpengaruh.

Mengatur tingkat isolasi TransactionScope

Beberapa konstruktor TransactionScope yang kelebihan beban menerima struktur jenis TransactionOptions untuk menentukan tingkat isolasi, selain nilai batas waktu. Secara default, transaksi dijalankan dengan tingkat isolasi diatur ke Serializable. Memilih tingkat isolasi selain Serializable merupakan praktik umum untuk sistem yang intensif membaca. Ini membutuhkan pemahaman yang kuat tentang teori pemrosesan transaksi dan semantik transaksi itu sendiri, masalah konkurensi yang terlibat, dan konsekuensi untuk konsistensi sistem.

Selain itu, tidak semua manajer sumber daya mendukung semua tingkat isolasi, dan mereka dapat memilih untuk mengambil bagian dalam transaksi pada tingkat yang lebih tinggi dari yang dikonfigurasi.

Setiap tingkat isolasi selain Serializable rentan terhadap inkonsistensi yang dihasilkan dari transaksi lain yang mengakses informasi yang sama. Perbedaan antara tingkat isolasi yang berbeda adalah cara kunci baca dan tulis digunakan. Kunci hanya dapat ditahan ketika transaksi mengakses data di resource manager, atau dapat disimpan hingga transaksi dilakukan atau dibatalkan. Yang pertama lebih baik untuk throughput, yang terakhir untuk konsistensi. Dua jenis kunci dan dua jenis operasi (baca/tulis) memberikan empat tingkat isolasi dasar. Lihat IsolationLevel untuk informasi lebih lanjut.

Saat menggunakan objek berlapis TransactionScope , semua cakupan berlapis harus dikonfigurasi untuk menggunakan tingkat isolasi yang sama persis jika mereka ingin bergabung dengan transaksi sekitar. Jika objek berganda TransactionScope mencoba menggabungkan transaksi sekitar namun menentukan tingkat isolasi yang berbeda, maka exception ArgumentException akan muncul.

Interoperabilitas dengan COM+

Saat membuat instans baru TransactionScope , Anda dapat menggunakan EnterpriseServicesInteropOption enumerasi di salah satu konstruktor untuk menentukan cara berinteraksi dengan COM+. Untuk informasi selengkapnya tentang ini, lihat Interoperabilitas dengan Layanan Perusahaan dan Transaksi COM+.

Lihat juga