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();
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();
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)
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.baglan.Close();
baglan.Open();
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
ahmet hocam çok teşekür ederim yardımınız işe yaradı.
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.