ADO.NET’te MARS özelliği [VB.NET, C#]

MARS(Multiple Active Result Sets), ADO.NET 2.0 ile birlikte geliştirilmiş olup tek connection üzerinden birden fazla query veya stored procedure(multiple queries) çalıştırmaya izin veren bir özelliktir. Bu özelliği kullanarak aynı bağlantı nesnesini kullanarak birden fazla veri seti üzerinde forward-only, read-only işlemler yapılabilir. MARS özelliğini kullanmadan bu tür işlemleri yapabilmek için aktif bağlantıyı kapatıp yeniden açmak veya birbirinden bağımsız bağlantı nesnelerini kullanmak gerekirdi. Yani gerek bağlantılı katmanda(DataReader) gerekse bağlantısız katmanda(DataSet) açık bir bağlantı nesnesini aynı anda sadece bir sorgulama işlemi için kullanabiliriz. Örneğin birçoğumuz, açık bağlantı gerektiren DataReader nesnesine ait bağlantı nesnesini, DataReader üzerinde işlem yaparken yeni bir sorgulama için kullanmak istediğimizde “There is already an open DataReader associated with this Connection which must be closed first.” hata mesajıyla karşılaşmışızdır. Çünkü açık olan bağlantı nesnesini aynı anda başka bir sorgulama için kullanamayız. Aşağıdaki örnekte açık olan bağlantı kapatmaksızın ikinci bir işlem için kullanmak istenmiştir.

Dim oCnn As New SqlConnection("Server=AHMETKAYMAZ;Database=Blog;Trusted_Connection=yes;")

Dim SqlQry1 As String = "SELECT * FROM Urun"
Dim SqlQry2 As String = "SELECT * FROM Marka"

Dim oCmd1 As New SqlCommand(SqlQry1, oCnn)
Dim oCmd2 As New SqlCommand(SqlQry2, oCnn)
oCnn.Open()
Dim oDr1 As SqlDataReader = oCmd1.ExecuteReader()
'oDr1 işlemleri 

Dim oDr2 As SqlDataReader = oCmd2.ExecuteReader()
'oDr2 işlemleri
oCnn.Close()
SqlConnection oCnn = new SqlConnection("Server=AHMETKAYMAZ;Database=Blog;Trusted_Connection=yes;");

string SqlQry1 = "SELECT * FROM Urun";
string SqlQry2 = "SELECT * FROM Marka";

SqlCommand oCmd1 = new SqlCommand(SqlQry1, oCnn);
SqlCommand oCmd2 = new SqlCommand(SqlQry2, oCnn);
oCnn.Open();
SqlDataReader oDr1 = oCmd1.ExecuteReader();
//oDr1 işlemleri

SqlDataReader oDr2 = oCmd2.ExecuteReader();
//oDr2 işlemleri
oCnn.Close();

Bu şekilde kodlarımızı çalıştırdığımızda oCmd2.ExecuteReader() satırında aşağıdaki hata mesajını alırız.

Açık olan bağlantıyı yeniden kullanmak için kapatıp yeniden açmamız gerekir.

Dim oCmd1 As New SqlCommand(SqlQry1, oCnn)
Dim oCmd2 As New SqlCommand(SqlQry2, oCnn)
oCnn.Open()
Dim oDr1 As SqlDataReader = oCmd1.ExecuteReader()
'oDr1 işlemleri
'Birinci sorgu için bağlantı kapatılır
oCnn.Close()

'Bağlantı başka bir sorguda kullanılmak üzere yeniden açılır
oCnn.Open()
Dim oDr2 As SqlDataReader = oCmd2.ExecuteReader()
'oDr2 işlemleri
oCnn.Close()
SqlCommand oCmd1 = new SqlCommand(SqlQry1, oCnn);
SqlCommand oCmd2 = new SqlCommand(SqlQry2, oCnn);
oCnn.Open();
SqlDataReader oDr1 = oCmd1.ExecuteReader();
//oDr1 işlemleri
//Birinci sorgu için bağlantı kapatılır
oCnn.Close();

//Bağlantı başka bir sorguda kullanılmak üzere yeniden açılır
oCnn.Open();
SqlDataReader oDr2 = oCmd2.ExecuteReader();
//oDr2 işlemleri
oCnn.Close();

Bu kod esnasında SQL Server Profiler aracına baktığımızda gerçekten de bağlantının kapatılıp yeniden açıldığını görebiliriz.

Açık olan bağlantıyı yeniden kullanabilmek için MARS özelliğini aktifleştirmek gerekir. Bunun için connection string içerisinde MultipleActiveResultSets parametresi true olarak set edilir. Örnekte kullandığımız connection string cümlesini aşağıdaki gibi düzenleyelim.
Server=AHMETKAYMAZ;Database=Blog;MultipleActiveResultSets=true; Uid=sa;Pwd=123
Bu sefer örneğimizi, Marka ve Urun tablosu hiyerarşik yapıda listelenecek şekilde değiştirelim. Yani her markanın altında o markaya ait ürünler görünecek. Burada yapacağımız işlem performans açısından iyi bir yöntem olmasa da markaları içeren DataReader nesnesini okuduğumuz döngüde o anki satırın MarkaId bilgisini alıp veri tabanında o MarkaId’ye denk gelen gelen ürünleri sorgulayacağız.

Dim oCnn As New SqlConnection(CnnStr)
Dim cmdMarka As New SqlCommand("SELECT * FROM Marka", oCnn)
Dim cmdUrun As New SqlCommand("SELECT * FROM Urun WHERE MarkaId=@MarkaId", oCnn)
cmdUrun.Parameters.Add("@MarkaId", SqlDbType.Int)

'SQL Server ile bağlantıyı kuralım
oCnn.Open()

Dim MarkaId As Integer

Using drMarka As SqlDataReader = cmdMarka.ExecuteReader()
    While drMarka.Read()
        Console.WriteLine(drMarka("MarkaAd"))
        'Okunan markanın altındaki ürünleri çekelim
        MarkaId = CInt(drMarka("MarkaId"))
        cmdUrun.Parameters("@MarkaId").Value = MarkaId
        Using drUrun As SqlDataReader = cmdUrun.ExecuteReader()
            While drUrun.Read()
                Console.WriteLine(Chr(9) & drUrun("UrunAd"))
            End While
        End Using
    End While
End Using

'SQL Server'daki bağlantımızı keselim
oCnn.Close()
SqlConnection oCnn = new SqlConnection(CnnStr);
SqlCommand cmdMarka = new SqlCommand("SELECT * FROM Marka",oCnn);
SqlCommand cmdUrun = new SqlCommand("SELECT * FROM Urun WHERE MarkaId=@MarkaId", oCnn);
cmdUrun.Parameters.Add("@MarkaId", SqlDbType.Int);

//SQL Server ile bağlantıyı kuralım
oCnn.Open();

int MarkaId;

using (SqlDataReader drMarka = cmdMarka.ExecuteReader())
{
    while (drMarka.Read())
    {
        Console.WriteLine(drMarka["MarkaAd"]);
        //Okunan markanın altındaki ürünleri çekelim
        MarkaId = (int)drMarka["MarkaId"];
        cmdUrun.Parameters["@MarkaId"].Value = MarkaId;
        using (SqlDataReader drUrun = cmdUrun.ExecuteReader())
        {
            while (drUrun.Read())
            {
                Console.WriteLine("\t{0}", drUrun["UrunAd"]);
            }
        }
    }
}

//SQL Server'daki bağlantımızı keselim
oCnn.Close();


Bu durumda iki record set’i işlemek için iki farklı connection nesnesi oluşturup sistem üzerinde ek yük oluşturmak yerine açık olan bağlantı üzerinden ikinci query de yürütülmüş oldu.
NOT : MARS özelliğini destekleyen ilk SQL Server sürümü, SQL Server 2005’tir. Bu yüzden MARS özelliğini aktifleştireceğimiz zaman karşı veri tabanının sürümünü de kontrol edebiliriz.

If oCnn.ServerVersion.StartsWith("09") Then
    'Aynı bağlantıyı kullanacak işlemler
End If
if (oCnn.ServerVersion.StartsWith("09")) {
    //Aynı bağlantıyı kullanacak işlemler
}

MARS işleminde transaction yönetimi kullanılacağı zaman aynı bağlantı içerisinden birden fazla transaction oluşturulamayacağı için aynı aktif connection nesnesini kullanacak tüm komut nesnelerinin aynı transaction nesnesiyle ilişkili olması ve SQL Server tarafında cursor açıkken commit işleminin yapılmıyor olması gerekir.

Dim SqlQry1 As String = "SELECT * FROM Marka"
Dim SqlQry2 As String = "UPDATE Marka SET MarkaAd = MarkaAd WHERE MarkaId = @MarkaId"

Dim oCmd1 As New SqlCommand(SqlQry1, oCnn)
Dim oCmd2 As New SqlCommand(SqlQry2, oCnn)
oCmd2.Parameters.Add("@MarkaId", SqlDbType.Int, 4)

oCnn.Open()

'Transaction nesnesi oluşturalım
Dim oTrs As SqlTransaction = oCnn.BeginTransaction()
'Her iki query, transaction yönetiminde olacak
oCmd1.Transaction = oTrs
oCmd2.Transaction = oTrs

'İlk querynin döndüreceği sonuçları okuyalım
Dim oDr As SqlDataReader = oCmd1.ExecuteReader()
While oDr.Read()
    'Satırları oku
    oCmd2.Parameters("@MarkaId").Value = oDr("MarkaId")
    oCmd2.ExecuteNonQuery()
    'Her update işleminden sonra commit etsin
    oTrs.Commit()
End While

oCmd1.Dispose()
oCmd2.Dispose()
oCnn.Close()
string SqlQry1 = "SELECT * FROM Marka";
string SqlQry2 = "UPDATE Marka SET MarkaAd = MarkaAd WHERE MarkaId = @MarkaId";

SqlCommand oCmd1 = new SqlCommand(SqlQry1, oCnn);
SqlCommand oCmd2 = new SqlCommand(SqlQry2, oCnn);
oCmd2.Parameters.Add("@MarkaId", SqlDbType.Int, 4);

oCnn.Open();

//Transaction nesnesi oluşturalım
SqlTransaction oTrs = oCnn.BeginTransaction();
//Her iki query, transaction yönetiminde olacak
oCmd1.Transaction = oTrs;
oCmd2.Transaction = oTrs;

//İlk querynin döndüreceği sonuçları okuyalım
SqlDataReader oDr = oCmd1.ExecuteReader();
while (oDr.Read())
{
    //Satırları oku
    oCmd2.Parameters["@MarkaId"].Value = oDr["MarkaId"];
    oCmd2.ExecuteNonQuery();
    //Her update işleminden sonra commit etsin
    oTrs.Commit();
}

oCmd1.Dispose();
oCmd2.Dispose();
oCnn.Close();


Bu hatayı aşmak için tüm command nesnelerini tek transaction içerisinde yürütmeliyiz. Bu da MARS’ın performans sorunu olarak karşımıza çıkmaktadır.

'İlk querynin döndüreceği sonuçları okuyalım
Dim oDr As SqlDataReader = oCmd1.ExecuteReader()
While oDr.Read()
    'Satırları oku
    oCmd2.Parameters("@MarkaId").Value = oDr("MarkaId")
    oCmd2.ExecuteNonQuery()
End While

'Tüm update işlemlerinden sonra COMMIT çalışsın
oTrs.Commit()
//İlk querynin döndüreceği sonuçları okuyalım
SqlDataReader oDr = oCmd1.ExecuteReader();
while (oDr.Read())
{
    //Satırları oku
    oCmd2.Parameters["@MarkaId"].Value = oDr["MarkaId"];
    oCmd2.ExecuteNonQuery();
}

//Tüm update işlemlerinden sonra COMMIT çalışsın
oTrs.Commit();

ADO.NET’te MARS özelliği [VB.NET, C#]” üzerine 5 düşünce

  1. Onur

    Ahmet hocam merhabalar yukarda anlattığınız problemi
    “There is already an open DataReader associated with this Connection which must be closed first.” bende yaşıyorum.Kendi kodlarımda acaba nerde düzeltme yapmalıyım.kodlar:

    SqlConnection baglan = new SqlConnection();
    SqlCommand sorgu = new SqlCommand();
    protected void Page_Load(object sender, EventArgs e)
    {
    baglan.ConnectionString = @"Data Source=ONUR\ONURSQL;initial Catalog=media;integrated security=sspi; ";
    baglan.Open();
    sorgu.Connection = baglan;
    sorgu.CommandText = "select * from Information";
    GridView1.DataSource = sorgu.ExecuteReader();
    GridView1.DataBind();
    }
    protected void Button1_Click(object sender, EventArgs e)
    string hucre = DropDownList1.SelectedValue ;
    string aranan = TextBox12.Text ;
    sorgu = new SqlCommand();
    sorgu.Connection = baglan;
    sorgu.CommandText = "select * from Information where " + hucre + " Like '%" + aranan + "%'";
    GridView1.DataSource = sorgu.ExecuteReader();
    GridView1.DataBind();
    }

    cevabınızı beklerim,teşekürler. protected void Page_Load(object sender, EventArgs e)
    {
    baglan.ConnectionString = @”Data Source=ONUR\ONURSQL;initial Catalog=media;integrated security=sspi; “; baglan.Open();
    sorgu.Connection = baglan;
    sorgu.CommandText = “select * from Information”; GridView1.DataSource = sorgu.ExecuteReader();
    GridView1.DataBind();
    } protected void Button1_Click(object sender, EventArgs e)
    string hucre = DropDownList1.SelectedValue ;
    string aranan = TextBox12.Text ;
    sorgu = new SqlCommand(); sorgu.Connection = baglan;
    sorgu.CommandText = “select * from Information where ” + hucre + ” Like ‘%” + aranan + “%'”; GridView1.DataSource = sorgu.ExecuteReader();
    GridView1.DataBind();

    Cevapla
  2. Ahmet Kaymaz Yazar

    Merhaba Onur,normal şartlar altında aynı SqlConnection nesnesini yeniden açmadan ikinci bir sorgu çalıştırılamaz. Nitekim aldığım hata da bunu anlatmaktadır. Öncelikle ASP.NET sayfalarının rutin yordamlarının çalışma mantığını bilmemiz gerekiyor. Bununla ilgili ASP.NET Page Life Cycle(Sayfa Yaşam Döngüsü) yazısına göz atmanı tavsiye ederim. Bu yazıda Page_Load() yordamının her postback sürecinde çalıştırıldığı anlatılmaktadır. Dolayısıyla senin örneğinde mantık çakışması yaşanıyor. Yani button tıklandığı anda önce Page_Load() yordamı çalıştırılır ardından Button1_Click() yordamı çalıştırılır dolayısıyla DataGrid nesnesini iki defa üst üstte bind etmeye çalışıyoruz. Bu yanlış bir mantık bunu engellemek için “baglan.ConnectionString” satırını baglan ve sorgu değişkenleri gibi global olarak tanımlayabilir ve Page_Load() içerisindeki tekrar etmesini istemediğimiz satırları if (!IsPostBack) {. . .} şartının içerisine eklemeliyiz. Böylece bu satırlar sadece sayfa ilk defa istendiğinde yani PostBack işlemi yapılmadığı zaman çalışır. Örnekte her iki yordamdaki sorgu aynı Connection nesnesi üzerinden yürütülmeye çalışıldığı için hata veriyor. Bu yüzden Button1_Click() yordamı içerisinde aşağıdaki gibi bağlantı açıksa onu kapatıp yeniden açmalıyız.
    if(baglan.State==ConnectionState.Open)
    baglan.Close();
    baglan.Open();
    Eğer böyle yapmak istemiyorsan ve SQL Server 2005 kullanıyorsan MARS özelliğini kullanabilirsin. Bunun için connection string’e “MultipleActiveResultSets=true;” ifadesini eklemen yeterlidir.

    Cevapla
  3. Coşkun erdoğan

    Ahmet bey C# kitabınızın 1. cildini aldım çok başarılı buldum. . . .Piyasadaki en iyi kitaplar arasında yere almaktadır. Bundan dolayı size teşekkür etmek istiyorum yalnız 2. cildini dost kitap evinde bulamadım. Ankarada nerede bulacağıma dair bilgi verirseniz çok sevinirim.Teşekkürler

    Cevapla
  4. Ahmet Kaymaz Yazar

    Coşkun Bey,İlginiz için teşekkür ederim. Serinin ikinci cildi şu anda basımda. Önümüzdeki hafta kitapçılara ulaşır diye ümit ediyorum.

    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.