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.
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
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.