Makale Özeti

COM+ Component'i oluşturmadan COM+ Servislerinden Faydalanmak makalemizin birinci bölümünde COM+ ile transaction servisini COM+ componenti oluşturmadan nasıl kullanacağımız incelemiş ve örneklendirmiştik. Bu makalemizde ise COM+ Transaction servisinden faydalanarak farklı sınıflar içerisinde çalıştırılan birbirinden bağımsız ancak iş kuralı olarak tek iş olan işlemleri tek bir transaction içerisinde çalıştıracağız.

Makale

COM+ Component'i oluşturmadan COM+ Servislerinden Faydalanmak - 2

COM+ Component'i oluşturmadan COM+ Servislerinden Faydalanmak makalemizin birinci bölümünde COM+ ile transaction servisini COM+ componenti oluşturmadan nasıl kullanacağımız incelemiş ve örneklendirmiştik.

Bu makalemizde ise COM+ Transaction servisinden faydalanarak farklı sınıflar içerisinde çalıştırılan birbirinden bağımsız ancak iş kuralı olarak tek iş olan işlemleri tek bir transaction içerisinde çalıştıracağız. Sipariş kayıdı girilirken Siparisler tablosuna kaydedilen sipariş bilgisi ile SiparisDetaylar tablosuna kaydedilen sipariş içerisindeki ürünleri belirleyen kayıtların tek bir transaction içerisinde yer almasını sağlayacağız.

Not: Bu senaryoda ki gibi aynı veritabanında bulunan iki tablo üzerinde yapılan işlemler zaten ADO.NET Transaction ile yapılabilir ve o şekilde yapılması performans açısından tavsiye edilmektedir. Ancak burada örneklendirilmek istenen farklı sınıflar içerisinde birbirinden tamamen habersiz olarak gerçekleştirilen ve DTC tarafından yönetilebilen işlerin aynı transactiona dahil edilmesidir. Kendi uygulamalarınız da uzakdaki bir sunucu üzerinde test etmek için sadece connectionstring değiştirerek ve makale sonunda bahsedilen yapılandırmayı yaparak bu işlemi gerçekleştirebilirsiniz.



Örnek uygulama tablo yapısı

Yukarıdaki şemaya uygun olarak verilerimizi taşıyacak ve veriler üzerinde CRUD (CREATE, READ, UPDATE, DELETE) işlemlerini gerçekleştirecek olan sınıflarımızı oluşturalım. Burada basitlik açısından sınıf tasarımlarında eksikler vardır. Şuan ki amacımız için gerekli olan özellik ve metodlar dahil edilmiştir.

Public Class Musteri
    Private _MusteriID As Integer
 
  Private _Ad As String
    Private _Email As String
 
   Public Property MusteriID() As Integer
        Get
            Return _MusteriID
        End Get
        Set(ByVal Value As Integer)
            _MusteriID = Value
        End Set
    End Property
    Public Property Ad() As String
        Get
            Return _Ad
        End Get
        Set(ByVal Value As String)
            _Ad = Value
        End Set
    End Property
    Public Property Email() As String
        Get
            Return _Email
        End Get
        Set(ByVal Value As String)
            _Email = Value
        End Set
    End
Property
End Class

 

Musteri sınıfı veritabanındaki müşteri bilgilerini taşımak için gerekli olan sınıftır. Örneğimiz için gerekli olmayan CRUD işlemleri için herhangi bir implementasyon yapılmamıştır.

 

Public Class SiparisDetay

    Private _Siparis As Siparis
    Private _Miktar As Integer
    Private _BirimFiyat As Decimal
    Private _UrunID As
Integer

    Public Property Siparis() As Siparis
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property Miktar() As
Integer
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property BirimFiyat() As
Decimal
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property UrunID() As
Integer
   
    'kodun sadeliği için silinmiştir
    End Property
 
   Public Sub New()
    End Sub
    Public Sub New(ByVal Miktar As Integer, ByVal BirimFiyat As Decimal, ByVal UrunID As Integer)
        Me.Miktar = Miktar
        Me.BirimFiyat = BirimFiyat
        Me.UrunID = UrunID
    End
Sub
 

    Public Sub Kaydet()
        Dim dal As New SqlDal
        dal.Params.Add("@SiparisID", Me.Siparis.SiparisID)
        dal.Params.Add("@UrunID", Me.UrunID)
        dal.Params.Add("@Miktar", Me.Miktar)
        dal.Params.Add("@BirimFiyat", Me.BirimFiyat)
        dal.ExecuteNonQuery("INSERT INTO SiparisDetaylar (SiparisID, UrunID, Miktar, BirimFiyat) VALUES (@SiparisID, @UrunID, @Miktar, @BirimFiyat)")
    End
Sub

End Class

Yukarıdaki kod bloğun yer alan sınıf bir siparis detay kayıdını temsil eden ve gerekli olan yeni kayıt eklemeyi sağlayan bir metodu (kaydet) içermektedir. Kod bloğu içerisinde yer alan property'lerin standart set ve get bölümleri makale içerisinde kodun sadeliğini sağlaması açısında silinmiştir. Örnek uygulama içerisinde ilgili bölümler tam ve çalışır halde yer almaktadır.

Bu sınıf kayıt işlemi için SqlDal Adındaki veri erişim sınıfını kullanmaktadır bu sınıf ile yapılan işlemler yeni bir connection üzerinden yapılmaktadır.

Imports System.Data.SqlClient
Imports System.EnterpriseServices
Public Class Siparis

    Private _SiparisDetaylar As New SiparisDetayCollection(Me)
    Private _SiparisID As Integer
    Private _Tarih As Date
    Private _Musteri As Musteri

    Public Property SiparisDetaylar() As SiparisDetayCollection
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property SiparisID() As
Integer
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property Tarih() As
Date
   
    'kodun sadeliği için silinmiştir
    End Property
    Public Property Musteri() As Musteri
   
    'kodun sadeliği için silinmiştir
    End Property

    Public Sub Kaydet()
       
Try
            Dim config As New ServiceConfig
            config.Transaction = TransactionOption.RequiresNew
            config.TrackingEnabled = True
            config.TrackingAppName = "Sipariş Bileşeni"
            config.TrackingComponentName = "Siparis"
            ServiceDomain.Enter(config)

            '----
            Dim dal As New SqlDal
            dal.Params.Add("@MusteriID", Me.Musteri.MusteriID)
            dal.Params.Add(New SqlParameter("@Tarih", SqlDbType.DateTime))
            dal.Params(1).Value = Me.Tarih
            Me.SiparisID = CType(
dal.ExecuteScalar("INSERT INTO Siparisler (MusteriID, Tarih) VALUES (@MusteriID, @Tarih);SELECT SCOPE_IDENTITY();"), Integer)

            For Each sipDetay As SiparisDetay In Me.SiparisDetaylar
              &n bsp;
sipDetay.Kaydet() 'sipariş içindeki sipariş detayları kaydet
            Next
            ContextUtil.SetComplete() 'işler yolunda tüm işlemleri onayla
        Catch ex As Exception
            ContextUtil.SetAbort() 'işler yolunda gitmedi transaction içindeki tüm işleri geri al
            Throw ex
        Finally
            ServiceDomain.Leave()
        End Try
    End Sub

End Class

Yukarıdaki kod içerisinde bir siparişi temsil etmek ve yeni bir sipariş kayıdı girebilmeyi sağlayan bir sınıf yer almaktadır. Bu sınıf içerisindeki Kaydet metodu dikkat etmemiz gerek bölümdür. Bu metod içerisinde önce kendi connectionı ile bir sipariş kayıdı girilmekte (INSERT INTO) daha sonra bu siparis nesnesinin SiparisDetaylar collectionı içerisideki tüm siparisdetay sınıflarının kaydet metodu çağrılmaktadır.

Ve bütün bu işlemle COM+ servislerinden faydalanabilmek için bir context oluşturulduğu sırada yapıldığı için yapılan tüm bu işlemler bir COM+ transactionı içerisinde yer almaktadır.

Yukarıdaki siparis sınıfı test etmek için bir windows form üzerine bir buton ekleyip aşağıdaki kodu yazınız.

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Try
            Dim mus As New Musteri
            mus.Ad = "Cengiz" : mus.Email = "cengiz@cengizhan.com"
            mus.MusteriID = 1

            Dim sip As New Siparis
            sip.Tarih = Now
            sip.Musteri = mus
            'ilk ürün
            Dim sipdetay As SiparisDetay = New SiparisDetay
            sipdetay.BirimFiyat = 100
            sipdetay.Miktar = 1
            sipdetay.UrunID = 1
            sip.SiparisDetaylar.Add(sipdetay)
            'ikinci ürün
            sip.SiparisDetaylar.Add(New SiparisDetay(2, 200, 2))
           
'siparişi kaydet
            sip.Kaydet()
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End
Sub

Yukarıdaki kodun click olayına yazıldığı butona tıklandığında aşağıdaki resimde gözüktüğü gibi bir COM+ Transactionı başlatılır.

Bu durumda COM+ Transactionı içerisinde yapılan sipariş kayıdı girme, sipariş için birinci sipariş detayını girme, sipariş için ikinci sipariş detayını girme işlemlerin hepsi onaylandığı takdirde işlemler ilgili veri kaynaklarına yansıtılacak aksi halde DTC servisi aracılığı ile işlemler onaylanmadan, geri alınacaktır.

Aslında bu örnek uygulama ile .NET Framework 2.0'da gelen ve heyecan uyandıran TransactionScope sınıfın yaptığı işlemin aynısı uygulamış oluyoruz.

Bu senaryoda kullanılan yöntemleri farklı konumlardaki veri kaynakları üzerinde çalıştırarak test edebilirsiniz. Örneğin Siparisler ve SiparisDetaylar tabloları farklı SQL Server'lar üzerinde oluşturulup(tamam pek gerçekçi değil:)), connectionstringler düzenlenip aynı işlem test edilebilir ve bu şekilde Distributed Transaction gerçekleştirilmiş olur.

Ancak...

DTC'nin farklı konumlardaki DTC'ler ile konuşabilmesi için DTC'nin Network Access'e sahip olması gerekir. Bu ayar varsayılan olarak kapalı gelir ve açılması gerekir. Aksi halde DTC'nin "enlist" olurken hata ile karşılaştığını anlatan bir hata mesajı alırsınız.

Bunun için;

Başlat/Çalıştır'a dcomcnfg yazıp Component Services yönetim aracını açın ve Component Services>Computers>My Computer üzerine sağ tıklayıp Properties seçeneğini seçin. Açılan diyalog penceresinden MSDTC sekmesine geçin, oradan da Security Configuration butonuna tıklayın.

DTC'nin network üzerinden işlem yapabilmesini sağlayın.

Yukarıdaki anlatılan adımların Distributed Transaction'a dahil olacak tüm bilgisayarlardaki DTC servisleri için gerçekleştirilmesi gereklidir. Yani hem koordinasyonu sağlayan (coordinating) hemde katılımcı (participating) DTC  servileri üzerinde bu işlem gerçekleştirilmelidir.

Not: Çalıştığınız işletim sistemine bağlı olarak Security Configuration penceresi farklı bir tasarım ile karşınıza gelebilir. Bu işlem ile ilgili olarak makalenin sonunda yer alan Microsoft Knowledge Base "How To" sunu okumanızı öneririm.

Ek kaynak : Windows Server 2003'te ağ DTC erişimi nasıl etkinleştirilir

Cengiz HAN
Microsoft ASP.NET MVP
cengiz@cengizhan.com

 

Uygulamada yer alan diğer sınıflar :
 

Imports System.Data.SqlClient

Public Class SqlDal

 

    Private _ConnectionString As String

    Private _Params As New SqlDalParameterCollection

    Public Property Params() As SqlDalParameterCollection

        Get

            Return _Params

        End Get

        Set(ByVal Value As SqlDalParameterCollection)

            _Params = Value

        End Set

    End Property

    Public Property ConnectionString() As String

        Get

            Return _ConnectionString

        End Get

        Set(ByVal Value As String)

            _ConnectionString = Value

        End Set

    End Property

 

    Public Sub New()

        Dim strCon As String = Configuration.ConfigurationSettings.AppSettings("strCon")

        If strCon Is Nothing Then

            Me.ConnectionString = "server=.;database=makaleDB;user id=sa;password=;"

        Else

            Me.ConnectionString = strCon

        End If

    End Sub

    Public Sub New(ByVal ConnectionString As String)

        Me.ConnectionString = ConnectionString

    End Sub

    Public Function ExecuteNonQuery(ByVal sql As String) As Integer

        Return ExecuteNonQuery(sql, CommandType.Text)

    End Function

    Public Function ExecuteNonQuery(ByVal sql As String, ByVal cmdType As CommandType) As Integer

        Dim con As New SqlConnection(Me.ConnectionString)

        Dim cmd As New SqlCommand(sql, con)

        cmd.CommandType = cmdType

        AddParams(cmd)

        con.Open()

        Dim affected As Integer = cmd.ExecuteNonQuery()

        con.Close()

        Return affected

    End Function

    Private Sub AddParams(ByVal cmd As SqlCommand)

        For Each param As SqlParameter In Me.Params

            cmd.Parameters.Add(param)

        Next

    End Sub

    Public Function ExecuteReader(ByVal sql As String) As SqlDataReader

        Return ExecuteReader(sql, CommandType.Text)

    End Function

    Public Function ExecuteReader(ByVal sql As String, ByVal cmdType As CommandType) As SqlDataReader

        Dim con As New SqlConnection(Me.ConnectionString)

        Dim cmd As New SqlCommand(sql, con)

        cmd.CommandType = cmdType

        AddParams(cmd)

        con.Open()

        Dim rdr As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)

        Return rdr

    End Function

    Public Function ExecuteScalar(ByVal sql As String) As Object

        Return ExecuteScalar(sql, CommandType.Text)

    End Function

    Public Function ExecuteScalar(ByVal sql As String, ByVal cmdType As CommandType) As Object

        Dim con As New SqlConnection(Me.ConnectionString)

        Dim cmd As New SqlCommand(sql, con)

        cmd.CommandType = cmdType

        AddParams(cmd)

        con.Open()

        Dim obj As Object = cmd.ExecuteScalar

        Return obj

    End Function

End Class

Public Class SqlDalParameterCollection

    Inherits CollectionBase

    Public Function Add(ByVal param As SqlParameter) As Integer

        Return List.Add(param)

    End Function

    Public Function Add(ByVal name As String, ByVal value As Object) As SqlParameter

        Dim param As New SqlParameter

        param.ParameterName = name

        param.Value = value

        Me.List.Add(param)

        Return param

    End Function

    Default Public Property Item(ByVal index As Integer) As SqlParameter

        Get

            Return CType(List(index), SqlParameter)

        End Get

        Set(ByVal Value As SqlParameter)

            List(index) = Value

        End Set

    End Property

End Class

 

Public Class SiparisDetayCollection

    Inherits CollectionBase

    Private _siparis As Siparis

    Public Sub New(ByVal siparis As Siparis)

        _siparis = siparis

    End Sub

    Public Function Add(ByVal sipdetay As SiparisDetay) As Integer

        sipdetay.Siparis = _siparis

        Return List.Add(sipdetay)

    End Function

    Default Public Property Items(ByVal index As Integer) As SiparisDetay

        Get

            Return CType(List(index), SiparisDetay)

        End Get

        Set(ByVal Value As SiparisDetay)

            List(index) = Value

        End Set

    End Property

End Class

Örnek Uygulama ve Veritabanı Scripti