Enes TAŞ

Developer

C# ADO.NET SqlTransaction Kavramı ve Kullanımı 17.08.2017

Bugün Sql Transaction kavramına ve bunun C# ADO.NET tarafında kullanımına değinmek istedim. 

Transaction nedir, neden kullanırız ve ne gibi durumlarda ya da işlemler de kullanırız öncelikle bunu ele alalım. 

Sql'de transaction blogları, peş peşe sağlanması gereken bir grup işlemin uygulanacağı durumlar da kullanılır. Örnek olarak birbiriyle bağlantılı olan bir işlem dizisi veya kısaca sorgular çalıştırmamız gerekiyorsa transaction kullanmamız en mantıklı durumdur. Transaction yapısının bize bu işlem de sağlayacağı fayda, sorguların herhangi birinde hata oluştuğu durumda öncesinde çalışan tüm işlemlerin geri alınmasıdır.  Böylece yapacak olduğumuz işlemin veri bütünlüğünü sağlamış ve herhangi bir veri kaybının önüne geçmiş oluruz. 

Şimdi bir örnekle bu durumu uygulayalım. Önce veritabanı işlemlerini sağlayacağımız bir sınıf oluşturalım. 

DBBaglanti.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace SqlTransactionKavrami.Lib {
    public class DBBaglanti {

        public string ConnectionString = "";
        public SqlConnection SqlBaglanti = new SqlConnection();

        /// <summary>
        /// Veritabanına bağlan
        /// </summary>
        public void BaglantiAc() {
            ConnectionString = @"Data Source=.; Initial Catalog=Veritabani_Adi; User Id=Sql_Kullanici_Adi; Password=Sql_Parola; Pooling=true";

            if (SqlBaglanti.State != ConnectionState.Open) {
                SqlBaglanti.ConnectionString = ConnectionString;
                SqlBaglanti.Open();
            }
        }


        /// <summary>
        /// Veritabanına bağlantıyı sonlandır
        /// </summary>
        public void BaglantiKapat() {
            if (SqlBaglanti.State != ConnectionState.Closed) {
                SqlBaglanti.Close();
                SqlBaglanti.Dispose();
            }
        }

        /// <summary>
        /// Transaction işlemini başlat
        /// </summary>
        /// <param name="transactionAdi"></param>
        /// <returns></returns>
        public SqlTransaction TransactionBaslat(string transactionAdi) {
            return SqlBaglanti.BeginTransaction(IsolationLevel.Serializable, transactionAdi);
        }

        /// <summary>
        /// Veritabanında Sorgu çalıştır
        /// </summary>
        /// <param name="sqlSorgusu"> Çalışacak sql sorgusu </param>
        /// <param name="transaction"> Transaction nesnemiz </param>
        /// <param name="param"> parametrelerimiz </param>
        public void KomutCalistir(string sqlSorgusu, SqlTransaction transaction, List<SqlParameter> param) {
            /* Sorgumuz için bir komut nesnesi oluşturalım */
            SqlCommand komut = SqlBaglanti.CreateCommand();
            try {
                /* transaction nesnemiz null değil ise bu komut işleminde kullanalım */
                if (transaction != null)
                    komut.Transaction = transaction;

                komut.CommandText = sqlSorgusu;

                /* parametre var ise sorgumuza ait parametreleri komut nesnemize ekleyelim*/
                if (param != null)
                    komut.Parameters.AddRange(param.ToArray());

                komut.CommandTimeout = 0;
                    
                /* komutumuzu çalıştıralım */
                komut.ExecuteNonQuery();
            }
            catch (SqlException ex ) {
                throw new Exception(ex.Message);
            }
            finally {
                komut.Dispose();
            }
        }
    }
}

Bağlantı sınıfımız da, Veritabanı bağlantısı oluşturma, bağlantıyı açma, kapatma, transaction işlemi başlatma ve sorgu çalıştırma işlemleri için metodlarımızı oluşturduk. 

Örnek için oluşturduğum veritabanında Siparis ve SiparisKalemleri adında iki tablom var. Bir sipariş örneği düşünecek olursak, önce Siparişi hangi müşterinin verdiğini ve sipariş toplamını, sonra siparişe ait ürün isimlerini ve fiyat, miktar gibi bilgilerini tutmamız gerekiyor. Müşterinin 10 kalem ürün siparişi verdiğini varsayarsak ve bu ürünler için oluşturduğumuz Insert sorgularından birinde bir hata aldık diyelim, eğer bu işlem de transaction bloglarını kullanmamış isek sipariş sisteme eksik kaydedilmiş olacaktır. 

Ado.Net'de bu işlemi SqlTransaction sınıfı ile sağlayabiliriz. Öncelikle transaction işlemini başlatacağız. 
Daha sonra çalıştıracak olduğumuz sorgularda bu işlemi kullanacağız. Eğer sorgularımız da bir hata yok ise transaction işlemimizi Commit edeceğiz, hata oluşmuş ise Rollback ederek tüm işlemleri geri alacağız. 

Bunun için şöyle bir örnek uyguladım. 

    private void btnCalistir_Click(object sender, EventArgs e) {
            /* Veritabanı bağlantı classımızı oluşturalım ve veritabanı bağlantımızı açalım */
            DBBaglanti baglan = new DBBaglanti();
            baglan.BaglantiAc();

            /* Sql Sorgularımız için bir transaction işlemi başlatalım */
            SqlTransaction transaction = baglan.TransactionBaslat("SiparisEkle");
            try {

                /* Sipariş tablomuza için sorgumuz ve parametrelerimiz */
                List<SqlParameter> param = new List<SqlParameter>();
                param.Add(new SqlParameter("@SiparisNo", "001"));
                param.Add(new SqlParameter("@MusteriAdi", "Enes TAŞ"));
                param.Add(new SqlParameter("@SiparisToplam", "100"));

                    
                string sql = "Insert into Siparis(SiparisNo,MusteriAdi,SiparisToplam) Values(@SiparisNo,@MusteriAdi,@SiparisToplam)";
               
                /* Bağlantı sınıfımızın KomutCalistir metoduna, sorgumuzu, transaction nesnemizi ve parametrelerimizi gönderiyoruz*/
                baglan.KomutCalistir(sql, transaction, param);

                /* Sipariş kalemleri tablomuz için 1. sipariş kalemi sorgumuz ve parametrelerimiz */
                param = new List<SqlParameter>();
                param.Add(new SqlParameter("@SiparisNo", "001"));
                param.Add(new SqlParameter("@UrunAdi", "Çakmak"));
                param.Add(new SqlParameter("@Fiyati", "22"));
                param.Add(new SqlParameter("@Miktari", "1"));
                param.Add(new SqlParameter("@Tutari", "22"));

                sql = "Insert into SiparisKalemleri(SiparisNo,UrunAdi,Fiyati,Miktari,Tutari) Values(@SiparisNo,@UrunAdi,@Fiyati,@Miktari,@Tutari)";
                baglan.KomutCalistir(sql, transaction, param);

                /* Sipariş kalemleri tablomuz için 2. sipariş kalemi sorgumuz ve parametrelerimiz */
                param = new List<SqlParameter>();
                param.Add(new SqlParameter("@SiparisNo", "001"));
                param.Add(new SqlParameter("@UrunAdi", "Sigara"));
                param.Add(new SqlParameter("@Fiyati", "13"));
                param.Add(new SqlParameter("@Miktari", "6"));
                param.Add(new SqlParameter("@Tutari", "78"));

                sql = "Insert into SiparisKalemleri(SiparisNo,UrunAdi,,Fiyati,Miktari,Tutari) Values(@SiparisNo,@UrunAdi,@Fiyati,@Miktari,@Tutari)";
                baglan.KomutCalistir(sql, transaction, param);
                
                /* Eğer sorgularımızın hiç birinde hata alınmamışsa işlem Commit olacak ve bilgiler veritabanına yazılacak*/
                transaction.Commit();

                MessageBox.Show("İşlem başarılı");
            }
            catch (Exception ex) {
                /* Sorgularımızın herhangi birinde hata alınmış ise tüm işlemler geri alınacak */
                transaction.Rollback();
                MessageBox.Show(ex.Message);
            }
            finally {
                transaction.Dispose();
                baglan.BaglantiKapat();
            }
        }

Bu işlemde arka arkaya çalışacak olan 3 adet Insert sorgusu var. Bilerek son sorguda bir virgül fazla ekleyerek syntax hatasına düşmesini sağladım. 

 sql = "Insert into SiparisKalemleri(SiparisNo,UrunAdi,,Fiyati,Miktari,Tutari) Values(@SiparisNo,@UrunAdi,@Fiyati,@Miktari,@Tutari)";            

Eğer bu işlemimiz de transaction kullanmasaydık ilk iki sorgumuz çalışacak ve sipariş veritabanına kaydedilecekti, ancak eksik kaydedilmiş olacaktı. Siparişe ait son ürün kaleminin bilgileri yer almayacaktı. 

Ancak transaction kullandığımız için hatadan önceki işlemlerin geri alınmasını sağlayarak veri bütünlüğünü koruduk ve veri kaybını engelledik. 

Umarım anlaşılır ve faydalı olmuştur. Örneğin daha detaylı halini buradan indirebilirsiniz. 



Etiketler

C#
Mirze - 08.07.2020 10:38

Tesekkur

gökhan beyaz - 06.05.2021 12:32

aslında , Siparis ve SiparisKalemleri isimli iki ayrı method olarak nasıl yapılır onu anlatmak gerek , iç içe 3-4 ayrı tablo için transaction işlemi yapılıyo genelde.Bu şekilde bir paylaşım daha iyi olacaktır.

Enes - 23.05.2021 00:26

Aynı connection ve transaction nesnesini kullanacak şekilde metodlara parçalanabilir evet ancak bunun için makale yazmaya gerek yok aslında, ihtiyaç doğrultusunda ihtiyaç duyan geliştirici bunu yapabilir kendisi. örneklemek gerekirse transaction connection düzeyinde başlatılıp, her metod aynı connection'ı parametre alabilir veya global bir connection nesnesi yaratılabilir bu işlem için. daha detaylı bilgi için unit of work paternini incelemenizi tavsiye ederim. hem ado.net hem entity framework örneklerini internette bulabilirsiniz.

Yorum Yap