15 Mayıs 2012 Salı

ADO.NET 'de MARS (Multiple Active Result Sets)

Merhaba arkadaşlar.
Bu yazımda "MARS" kavramı nedir ve neden kullanmamız gerekir, bundan bahsedeceğim. Ado.Net 'e giriş yapmıştık  ve "select, insert, update, delete" sorgularını kullanarak veritabanımızdan verilerimizi çekmiştik. Ado.Net bir programlama dili değil bir namespace yani sınıf kütüphanesidir. Bu sınıfları kullanarak database deki veriye kolayca erişebiliyorduk. "Connected" ve "disconnected" olmak üzere iki çeşit veriye erişim mimarisi vardır. Bu mimarileri kullanarak, verilerimizi manipüle edebiliyoruz. "Connected" mimaride, Sql Server 'a bağlanmamızı sağlayan Sql Server 'a özel "SqlConnection, SqlComman, SqlDataReader.." gibi sınıflar vardı ve bağlantıyı biz elle kontrol ediyorduk. Kendimiz "connection.Open()" diye açıp, "connection.Close()" diye kapatıyorduk ve bu arada komutlarımızı çalıştırıyorduk. Veritabanından satır satır veri okumak istiyorsak komutumuzu, bu açık bağlantı üzerinden, "DataReader" nesnesi ile komutumuzu çalıştırmamız gerekiyordu. Peki aynı bağlantı üzerinden aynı anda, birden fazla "DataReader" nesnesi kullanarak, veritabanından yine satır satır veri okumam gerekirse ne yapmam gerekir?  İşte arkadaşlar bu durumda "connection string(bağlantı cümleciği)" 'e "Multiple Active Result Sets=True" parametresini vermek durumundayız. MARS, aynı açık bağlantı üzerinden birden fazla satır kümesini elde edebileceğimiz sql komutlarının, eş zamanlı olarak çalıştırılmasına imkan verir. Böylece birden fazla komutum senkron çalışarak, aynı anda birden fazla açık "DataReader" nesnesi aynı bağlantı üzerinden veri okuyabilecektir. Bu parametrenin varsayılan değeri "false" 'dur. Ado.Net 2.0 ve Sql Server 2005 versiyonlarından itibaren bu özellik desteklenmektedir.

NOT: "Insert, update, delete" komutlarında "MARS" özelliğini "true" yapmamıza gerek yok. Çünkü open close yapıları kendi içlerinde yapılır.

Hemen bir örnek üzerinden nasıl kullanılması gerekir onu inceleyelim. Bir tane "Form Application" açıyorum. Formuma bir "treeview" kontrollerinden atıyorum. "Form_Load" event 'inde de aşağıdaki kodlarımı yazıyorum. "Northwind" veritabanını kullanacağım. "Customers" tablosundan "country" kolonunu "Treeview" kontrolüne, benzersiz bir şekilde getirelim. Olduğu gibi getirirsem, aynı ülkede birden fazla müşteri olduğundan dolayı tekrar eden müşteriler de gelecektir. Sorgumuz çalıştığında, veri tabanından bir ülke gelirken, bir yandan da başka bir sorguyla bu ülkedeki tüm müşterileri o ülkenin altında listeleyelim. Yani iki tane "select" sorgumuz olacak ve birinci sorgumuz, ikinci sorgumuza parametre gönderecektir. İlk önce MARS kullanmazsak, nasıl bir hata ile karşılaşacağız ona bir bakalım.

SqlConnection connection = new SqlConnection("Server=. ; Database=Northwind ; Uid=sa ; Pwd=1 ; MultipleActiveResultSets=false");
SqlCommand command = new SqlCommand("SELECT distinct country FROM Customers", connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
    string country = reader.GetString(0);
    TreeNode nodeCountry = new TreeNode(country);
    treeView1.Nodes.Add(nodeCountry);
    SqlCommand commandCustomer = connection.CreateCommand();
    commandCustomer.CommandText = "SELECT CompanyName FROM Customers WHERE Country=@c";
    commandCustomer.Parameters.AddWithValue("@c", country);
    SqlDataReader readerCustomer = commandCustomer.ExecuteReader();
    while (readerCustomer.Read())
    {
        string companyName = readerCustomer.GetString(0);
        TreeNode nodeCustomer = new TreeNode(companyName);
        nodeCountry.Nodes.Add(nodeCustomer);
    }
}
connection.Close();
Hata mesajında da görüldüğü üzere arkadaşlar, kapatılması gereken zaten açık bir "DataReader" nesnesi var.  Biz ilk kod satırındaki bağlantı cümleciğimize "MultipleActiveResultSets=true" vererek, birden fazla açık "DataReader" nesnelerini desteklemiş oluyoruz. "MultipleActiveResultSets=true"ekledik ve projemizi çalıştırdık.
NOT: Son satırda bulunan "connection.Close()" komutu ile sadece connection değil o connection üzerindeki tüm nesneler de kapatılır.

Görüldüğü üzere arkadaşlar "MultipleActiveResultSets=true" yazmamız şart. Peki "DataRaeder" nesnesiyle çekilen birbirinden bağımsız sorgular da çalıştırabiliriz. Tabiki bu sorgular asenkron olarak çalışacaktır. Yani aynı anda değil sırayla. Örneğin formumda iki tane "ListBox" kontrolü olsun. Birinci  ListBox 'da "Products" tablosundan "ProductName" kolonunu, ikinci ListBox 'da ise "Categories" tablosundan "CategoryName" kolonunu listeleyelim. "MARS" özelliğini true yapmadan iki tane çözüm vardır. Birincisi aşağıdaki gibi ilk sorgudan sonra "readerCategory" nesnemi kapatabilirim. Daha sonra "readerProduct" nesnem açılacaktır. Böylelikle aynı anda iki "reader" açık olmayacaktır. 

SqlConnection connection = new SqlConnection("Server=. ; Database=Northwind ; trusted_connection=true");         
SqlCommand commandCat = new SqlCommand("SELECT CategoryName FROM Categories", connection);
SqlCommand commandPro = new SqlCommand("select ProductName from Products", connection);
connection.Open();
SqlDataReader readerCategory = commandCat.ExecuteReader();
while(readerCategory.Read())
       listBox1.Items.Add(readerCategory.GetString(0));
readerCategory.Close();
SqlDataReader readerProduct = commandPro.ExecuteReader();
while(readerProduct.Read())
       listBox2.Items.Add(readerProduct.GetString(0));
connection.Close();


İkincisi çözüm ise aşağıdaki gibi her bir SqlDataReader nesnesini kendi SqlConnection havuzu içinde çalıştırmak. SqlConnection sınıfı ile iki tane bağlantı oluşturmak. Her bağlantıda bir sorgu çalıştırmak. 

SqlConnection connection1 = new SqlConnection("Server=. ; Database=Northwind ; trusted_connection=true");
SqlConnection connection2 = new SqlConnection("Server=. ; Database=Northwind ; trusted_connection=true");
SqlCommand commandCat = new SqlCommand("SELECT CategoryName FROM Categories", connection1);
SqlCommand commandPro = new SqlCommand("select ProductName from Products", connection2);           
connection1.Open();
SqlDataReader readerCategory = commandCat.ExecuteReader();
while (readerCategory.Read())
       listBox1.Items.Add(readerCategory.GetString(0));
connection1.Close();
connection2.Open();
SqlDataReader readerProduct = commmandPro.ExecuteReader();
while (readerProduct.Read())
       listBox2.Items.Add(readerProduct.GetString(0));
connection2.Close();

Bu teknik her ne kadar çözümmüş gibi görünse de her sorgu için bir kaynak ayrılmış olduğundan gereksiz yere kaynak tüketimlerine sebep olur. Çünkü "Open-Close" deyimleri ile çalışan nesneler kaynak tüketirler. İşlemciyi ve remi yorarlar. Ayrıca bu kullanımda sorgular sırayla çalışacaktır. Biri bitmeden diğeri çalışmayacaktır. Bunun için de "multi-threading" (çok kanallı) uygulamalara ihtiyaç duyulacaktır. Hiç gerek yok.
Bu sebeplerden dolayıdır ki "DataRaeder" nesnesiyle çekilen birbirinden bağımsız sorgularda dahil bu iki çözüm yerine "MARS" kullanmak daha mantıklıdır. Ne "reader" kapatmam gerekir ne de yeni bir "connection" yaratmam.

Umarım faydalı olabilmişimdir. Başka bir yazıda görüşmek dileğiyle, hoşçakalın..

Hiç yorum yok:

Yorum Gönder