ADO.NET Transaction İşlemi – II [ADO.NET 2.0]

ADO.NET 2.0 ile birlikte programcının yerel ve dağıtık transaction işlemlerini daha kolayca yönetebilmesi için System.Transactions.dll kütüphanesi sunuldu. ADO.NET 2.0’da LightWeight Transaction(LT) ve OLE Transaction(OleTx) olmak üzere iki transaction modeli geliştirildi.
LightWeight Transaction, tek veri kaynağı üzerinde sorgulama yapılırken kullanılan transaction modeli, OleTx Transaction ise dağıtık transaction yapısında olup ayrı veri kaynakları üzerinde işlem yapılırken kullanılan modeldir.
Bu yeni transaction sistemini kullanabilmek için projemize System.Transactions.dll dosyasını referans olarak eklememiz gerekir.

Transaction yönetimi için TransactionScope sınıfı kullanılır. Bu sınıfı kullanarak transaction yönetiminde çalışacak nesneler için bir etki alanı(scope) oluşturulur. Transaction bloğu, .NET 2.0 ile birlikte gelmiş olan “using” ifadesiyle oluşturulur.
Önceki sayfalarda anlattıldığı üzere ADO.NET’te klasik transaction yönetimi aşağıdaki gibi yapılır.

Dim oCnn As New SqlConnection(CnnStr)
Dim sQry As String = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;"
Dim oCmd As New SqlCommand(sQry, oCnn)
oCnn.Open()

'Transaction nesnesi oluşturalım
Dim oTrs As SqlTransaction = oCnn.BeginTransaction()
'Transaction nesnesini Command nesnesiyle ilişkilendirelim
oCmd.Transaction = oTrs

Try
    oCmd.ExecuteNonQuery()
'Sorgu başarıyla yürütüldüğü için işlemi kalıcı yapalım
    oTrs.Commit()
Catch
'Sorgu başarısız olduğu için işlemi geri alalım
    oTrs.Rollback()
Finally
'Her durumda bağlantıyı kapat
    oCmd.Dispose()
    oCnn.Close()
End Try
SqlConnection oCnn = new SqlConnection(CnnStr);
string sQry = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;";
SqlCommand oCmd = new SqlCommand(sQry, oCnn);
oCnn.Open();

//Transaction nesnesi oluşturalım
SqlTransaction oTrs = oCnn.BeginTransaction();
//Transaction nesnesini Command nesnesiyle ilişkilendirelim
oCmd.Transaction = oTrs;

try
{
    oCmd.ExecuteNonQuery();
    //Sorgu başarıyla yürütüldüğü için işlemi kalıcı yapalım
    oTrs.Commit();
}
catch
{
    //Sorgu başarısız olduğu için işlemi geri alalım
    oTrs.Rollback();
}
finally
{
    //Her durumda bağlantıyı kapat
    oCmd.Dispose();
    oCnn.Close();
}

Bu işlemi TransactionScope sınıfını kullanarak daha az kodla gerçekleştirebiliriz. Transaction işlemini başarılı olduğunda işlemleri kalıcı yapmak için TransactionScope sınıfının Complete() metodu kullanılır. TransactionScope nesnesinin kullanım biçimi şu şekildedir;

Using scope As New TransactionScope()
    'Transaction tabanlı işlemler
    'Herhangi bir hata oluşmazsa Transaction'ı commit et
    scope.Complete()
End Using
using(TransactionScope scope = new TransactionScope())
{
   /* Transaction tabanlı işlemler*/
   //Herhangi bir hata oluşmazsa Transaction'ı commit et
   scope.Complete();
}

Önceki örneği, bu yeni yöntemle yazalım.

Imports System.Transactions
.....
Using oTrs As New TransactionScope()
    Dim CnnStr As String = "Server=AHMETKAYMAZ;Database=Kitap;Uid=sa;Pwd=123;"
    Dim oCnn As New SqlConnection(CnnStr)
    Dim sQry As String = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3;"
    Dim oCmd As New SqlCommand(sQry, oCnn)
    oCnn.Open()

    Try
        oCmd.ExecuteNonQuery()
    'Transaction'ı COMMIT et
        oTrs.Complete()
    Catch ex As Exception
        Console.WriteLine("HATA OLUŞTU : " + ex.Message)
    'using bloğu içinde olduğumuz için Connection ve Command nesneleri dispose edilir.
    End Try
End Using
using System.Transactions;
.....
using (TransactionScope oTrs = new TransactionScope())
{
    string CnnStr = "Server=AHMETKAYMAZ;Database=Kitap;Uid=sa;Pwd=123;";
    SqlConnection oCnn = new SqlConnection(CnnStr);
    string sQry = "UPDATE Musteri SET AdSoyad='Ayşe Güler1' WHERE MusteriId=3;";
    SqlCommand oCmd = new SqlCommand(sQry, oCnn);
    oCnn.Open();

    try
    {
        oCmd.ExecuteNonQuery();
        //Transaction'ı COMMIT et
        oTrs.Complete();
    }
    catch(Exception ex)
    {
        Console.WriteLine("HATA OLUŞTU : "+ ex.Message);
    }
    //using bloğu içinde olduğumuz için Connection ve Command nesneleri dispose edilir.
}

Aynı şekilde iki farklı Command nesnesini de transaction yönetimine alabiliriz. Bu örnekte tek veri kaynağı üzerinde işlem yapıldığı için LightWeight Transaction tipi kullanılmış oldu. TransactionScope nesnesinin asıl kolaylığını OleTx Transaction tipi üzerinde görebiliriz. Aşağıdaki örnekte iki farklı veritabanı sunucusu üzerinde işlem yapılarak dağıtık transaction ortamı oluşturulmuştur. İki farklı veritabanı sunucusu üzerinde işlem yapıldığı için ADO.NET, programcının ek birşey yapmasına fırsat vermeyerek bu işlem dağıtık transaction yöntemini kullanacaktır.

Using oTrs As New TransactionScope()

    Dim CnnStr1 As String = "Server=DW1;Database=DB;Uid=sa;Pwd=sa;"
    Dim CnnStr2 As String = "Server=DW2;Database=DB;Uid=sa;Pwd=sa;"

    Dim oCnn1 As New SqlConnection(CnnStr1)
    Dim oCnn2 As New SqlConnection(CnnStr2)

    Dim sQry1 As String = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;"
    Dim sQry2 As String = "UPDATE Urun SET Fiyat='35' WHERE UrunId=12;"

    Dim oCmd1 As New SqlCommand(sQry1, oCnn1)
    Dim oCmd2 As New SqlCommand(sQry2, oCnn2)

    Try
        oCnn2.Open()
        oCnn1.Open()

        oCmd1.ExecuteNonQuery()
        oCmd2.ExecuteNonQuery()

        oTrs.Complete()
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try
End Using
using (TransactionScope oTrs = new TransactionScope())
{

    string CnnStr1 = "Server=DW1;Database=DB;Uid=sa;Pwd=sa;";
    string CnnStr2 = "Server=DW2;Database=DB;Uid=sa;Pwd=sa;";

    SqlConnection oCnn1 = new SqlConnection(CnnStr1);
    SqlConnection oCnn2 = new SqlConnection(CnnStr2);

    string sQry1 = "UPDATE Musteri SET AdSoyad='Ayşe Güler' WHERE MusteriId=3;";
    string sQry2 = "UPDATE Urun SET Fiyat='35' WHERE UrunId=12;";

    SqlCommand oCmd1 = new SqlCommand(sQry1, oCnn1);
    SqlCommand oCmd2 = new SqlCommand(sQry2, oCnn2);

    try
    {
        oCnn2.Open();
        oCnn1.Open();

        oCmd1.ExecuteNonQuery();
        oCmd2.ExecuteNonQuery();

        oTrs.Complete();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}//using

Dağıtık transaction işlemleri Microsoft Distributed Transaction Coordinator(MSDTC) tarafından yönetilir. Bu servisi doğru bir şekilde yapılandırdıktan sonra aynı application domain içerisindeki iki farklı sunucu bağlantısı üzerinde etkin bir transaction yönetimi gerçekleştirmiş oluruz. DTC, her iki veritabanı sunucu üzerindeki DTC’lerden işlemleri doğru yapılıp yapılmadığına dair geri bildirim bekler. Gelen sonuç olumluysa TransactionScope nesnesi, Complete() edilir. Görüldüğü gibi ADO.NET 2.0’daki bu özellik, programcının COM+ programlamayla uğraşmasına gerek kalmadan System.Enterprise servislerini kullanmadan dağıtık transaction işlemini daha az kodla kolaylıkla ve daha performanslı gerçekleştirmeyi sağlamıştır.
TransactionScope sınıfının constructor’ü overload edilmiş olup aktif transaction’ın davranış biçimini ve zaman aşım süresini parametre olarak alır. Bu parametreler daha çok iç içe kullanılmış transaction(nested transaction) yönetiminde işlev kazanmaktadır. Nested transaction, bir transaction’nın başarıyla sonuçlanması için başka bir transaction’ın başarıyla bitmesini beklemesidir. ADO.NET ile bu içiçe hareket işlemleri de kolaylıkla yapılabilmektedir. Nested transaction, direk veya dolaylı yapılabilir.

[Dolaysız]
Using scope1 As New TransactionScope()
    Using scope2 As New TransactionScope()
        scope2.Complete()
    End Using
    scope1.Complete()
End Using

[Dolaylı]
Sub Main()
    Using scope As New TransactionScope()
        'Transaction tabanlı işlemler
        AraMetod()
        scope.Complete()
    End Using
End Sub

Sub AraMetod()
    Using scope As New TransactionScope()
        'Transaction tabanlı işlemler
        scope.Complete()
    End Using
End Sub
[Dolaysız]
using (TransactionScope scope1 = new TransactionScope())
{
    using (TransactionScope scope2 = new TransactionScope())
    {
        scope2.Complete();
    }
    scope1.Complete();
}

[Dolaylı]
void Main()
{
   using(TransactionScope scope = new TransactionScope())
   {
      /* Transaction tabanlı işlemler */
      AraMetod();
      scope.Complete();
   }
}//Main

void AraMetod()
{
   using(TransactionScope scope = new TransactionScope())
   {
      /* Transaction tabanlı işlemler */
      scope.Complete();
   }
}//AraMetod

TransactionOptions sınıfı kullanılarak transaction bloğu için izolasyon seviyesi ve zaman aşımı süresi de tanımlanabilir. Aynı zamanda nested transaction işleminde içteki transaction blokları için hangi scope’da etkili olacağını belirleyebiliriz. Bunun için TransactionScopeOption enum’u kullanılır.

IsolationLevel, kullanıcı işlemlerin veya başka transaction’ın o anki transaction’a olan duyarlılığını yani aktif transaction çalışırken diğer transaction’ların aktif transaction yönetimindeki verilere nasıl davranacağını bildirir. Transaction’daki izolasyon seviyeleri (Isolation Level), aynı verilere yapılan ortak zamanlı erişim sorunlarını çözmek için kullanılır. Burada bu seçeneklerle ilgili bir iki cümle yazmak faydalı olacaktır.
Chaos: Değiştirmeye çalıştığımız veriler üzerinde başkası güncelleme yapıyorsa bizim değiştirmemize izin verilmez. Bu seçenek, SQL Server tarafından desteklenmemektedir.
ReadCommited: Okumaya çalıştığımız veriler üzerinde o anda başkası güncelleme yapıyorsa o kişinin ancak COMMIT veya ROLLBACK ettiği verilerini okuyabiliriz. Örneğin bir tablodaki X satırını okuduk ve okuma transaction’u açık bıraktık. Bu arada başka bir transaction bu satırı güncelledi fakat COMMIT etmedi. Bu durumda okuma transaction’ı aynı satırı okumaya çalıştığında hata oluşur(Dirty Read sorunu). Transaction işlemi, default olarak ReadCommited seçeneğine sahiptir.
ReadUncommited: En düşük isolation seviyesi olup diğer transaction’ların henüz COMMIT edilmemiş verilerini de okuyabilmeyi sağlar. İkinci transaction tarafından yapılmış değişikliklerin birinci transaction tarafından okunabilmesi için ikinci transaction’ın COMMIT veya ROLLBACK yapması beklenmez. Yani önceki seçenekte bahsettiğimiz “Dirty Read” durumuna onay verir.
RepeatableRead: Diğer transaction’lara ait COMMIT edilmiş verilere erişilir ancak veriler okunurken diğer kullanıcıların o veriler üzerinde değişiklik yapmasını engeller. Yani SELECT işlemi çalışırken o anda başka bir kullanıcının UPDATE, DELETE işlemi yapması engellenir fakat INSERT yapmasına izin verilir.
Serializable: En yüksek isolation seviyesi olup verileri okurken veritabanını kilitleyerek diğer kullanıcıların hiçbir şekilde hiçbir ekleme, güncelleme yapmaması sağlar. Bir transaction’ın ekleme, güncelleme, silme yapabilmesi için aktif transaction’ın bitmesini bekler. UPDATE, DELETE, INSERT işlemi yapılırken hiçbir şekilde o anda başka bir kullanıcı aynı verileri SELECT edemez. Bu seçenek, performans açısından tercih edilmez ve aktif transaction uzun sürdüğü zaman deadlock denilen ölümcül kilitlenme sorunu yaşanabilir.
Diğer konu, iç içe olan transaction bloklarının faaliyet alanlarının belirlenmesidir. Bunun için TransactionScopeOption seçenekleri kullanılır. Bu seçenekler, içteki transaction’ın üstteki transaction’ın scope’ına eklenip eklenmeyeceğini bildirir;
Required: Varsa önceki transaction’a(Ambient Transaction) ait scope’a ekler yoksa yeni bir scope yaratır. Yani daha önce oluşturulmuş bir transaction varsa yeni işlemi o transaction’a dahil eder yoksa yeni bir transaction başlatır.
RequiresNew: Yeni bir transaction scope yaratır varsa öncekini askıya alır. Root scope kendisi olmuş olacak.
Suppress: Herhangi bir transaction içerisine dahil etmez.
Aşağıdaki tabloda TransactionOptions sınıfının kullanımı gösterilmiştir;

Dim oOpts As New TransactionOptions()
oOpts.IsolationLevel = IsolationLevel.ReadCommitted
oOpts.Timeout = New TimeSpan(0, 0, 60)

Using oTrs As New TransactionScope(TransactionScopeOption.RequiresNew, oOpts)
    '.....
End Using
TransactionOptions oOpts = new TransactionOptions();
oOpts.IsolationLevel = IsolationLevel.ReadCommitted;
oOpts.Timeout = new TimeSpan(0, 0, 60);

using (TransactionScope oTrs = new TransactionScope(
        TransactionScopeOption.RequiresNew,oOpts))
{
    .....
}

İç içe transaction’ların scope belirlemelerini bir şekilde üzerinde gösterelim.

Sub Main(ByVal args As String())
    Method1()
    Method3()
End Sub

Sub Method1()
    Using scope1 As New TransactionScope(TransactionScopeOption.Required)
        Method2()
        scope1.Complete()
    End Using
End Sub

Sub Method2()
    Using scope2 As New TransactionScope(TransactionScopeOption.RequiresNew)
        Method3()
        Using scope4 As New TransactionScope(TransactionScopeOption.Suppress)
            scope4.Complete()
        End Using
        scope2.Complete()
    End Using
End Sub

Sub Method3()
    Using scope3 As New TransactionScope(TransactionScopeOption.Required)
        scope3.Complete()
    End Using
End Sub
static void Main(string[] args){
    Method1();
    Method3();
}//Main

public static void Method1()
{
    using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required))
    {
        Method2();
        scope1.Complete();
    }
}//Method1

public static void Method2()
{
    using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        Method3();
        using (TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
        {
            scope4.Complete();
        }
        scope2.Complete();
    }
}//Method2

public static void Method3()
{
    using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Required))
    {
        scope3.Complete();
    }
}//Method3

Bu örnek, simpleisbest.net’ten alınmıştır.

ADO.NET Transaction İşlemi – II [ADO.NET 2.0]” üzerine bir düşünce

  1. Can KAYA

    gerçekten süper bir anlatım olmus distrubuted transactionalda COM+ componenti ile ugrasma derdimiz kalmadı. Ado.net 2.0 ile gelen bu özellik ise bize neredeyse hiçbirşey bırakmamıs herseyi halletmiş. özellikle işlem önceligi gibi transactionscope leri commit etmesi ise bir harika. elinize saglık. harikulade bir makale olmus

    Cevapla
  2. Volkan Günay

    Verdiğiniz bilgiler için teşekkürler. Örneğinizden yola çıkarak yapmak istediğim bir iş ile ilgili sorum olacaktı. İki ayrı serverdan birinden select yapıp, diğerine select sonucu dönen kayıtları insert için de bu yöntemi uygulayabilir miyiz? Select sonucu dönen kayıtları insert ederken hata oluşursa komple rollback olur mu? Yoksa bu iş için farklı bir işlem mi kullanmak gereklidir.

    Cevapla

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Time limit is exhausted. Please reload CAPTCHA.