https://buraksenyurt.com/Burak Selim Şenyurt - Ado.Net2024-03-14T08:15:06+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/SqlCommandBuilder-icin-4-Tavsiye-bsenyurt-com-danSqlCommandBuilder için 4 Tavsiye2006-11-13T12:00:00+00:00bsenyurt<p>Değerli Okurlarım Merhabalar,</p>
<p>SqlCommandBuilder sınıfı özellikle bağlantısız katman (disconnected layer) modelinde sıkça kullanılmaktadır. Çoğunlukla, SqlDataAdapter tipine ait nesneler için gerekli olan UpdateCommand, InsertCommand ve DeleteCommand özelliklerine bağlı SqlCommand nesnelerini sıfırdan oluşturmamak için tercih edilebilir. Framework 1.1' de özellikle bağlantısız katman modeline ait bir vakka olan Concurency Violation durumlarındaki yaklaşımı nedeniyle (tüm kolonları where' e dahil etmek) bazen tercih edilmemektedir.</p>
<p>Ancak SqlCommandBuilder, Framework 2.0 ile birlikte kendisine eklenen yeni üyeler sayesinde daha da fonksiyonel hale gelmiştir. Bununla birlikte SqlCommandBuilder tipinin sadece bağlantısız katman (disconnected layer) modeli için yazılmış olduğunu düşünmek haksızlık olacaktır. Nitekim katmanlı mimaride veri erişim katmanı (data access layer) içerisinde son derece kullanışlı olabilecek bir metodada sahiptir. İşte bu makalemizde SqlCommandBuilder tipinin, .Net Framework 2.0 versiyonu ile birlikte güçlendirilmiş olan yönlendiren bahsetmeye çalışacağız. Temel olarak işleyeceğimiz maddeler aşağıdaki gibidir.</p>
<ul>
<li>Stored Procedure' lerin parametrik yapısını çalışma zamanında bir SqlCommand nesnesine aktarabilme</li>
<li>Çakışma durumları için uygun olan yöntemi <strong>ConflictOption</strong> özelliği ile belirleyebilme</li>
<li>Update işlemleri sırasında sadece güncellenen parametreleri sql sunucusuna gönderebilme</li>
<li>SqlDataAdapter için üretilen sql komutlarında kolon adı kullanılmasını tercih edebilme</li>
</ul>
<p>Şimdi burada bahsettiğimiz özellikleri incelemeye çalışalım. Ancak maddelerimizi incelemeden önce senaryo olarak kullanacağımız tablomuzu aşağıdaki sql script yardımıyla oluşturalım. Personel isimli tablomuz Sql Server 2005 üzerinde AdventureWorks veritabanı altında yer alacaktır. Ancak örneklerimiz için dilerseniz siz kendi tablolarınızıda kullanabilirsiniz.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">USE [AdventureWorks]
GO
CREATE TABLE [dbo].[Personel](
[PersonelId] [int] IDENTITY(1,1) NOT NULL,
[Adi] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Soyadi] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Maasi] [money] NOT NULL,
[IseGirisTarihi] [datetime] NOT NULL,
[Departmani] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Durum] [timestamp] NOT NULL,
CONSTRAINT [PK_Personel] PRIMARY KEY CLUSTERED
(
[PersonelId] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]</pre>
<p><strong>1. Stored Procedure' lerin parametrik yapısını çalışma zamanında bir SqlCommand nesnesine aktarabilme</strong></p>
<p>Öyle bir metod düşünelim ki, sadece çalıştıracağı saklı yordamın (stored procedure) adını, ve sayısını bilmemesine rağmen parametrelerinin değerlerini alsın. Sonrada işaret ettiği bu saklı yordamı yürütsün. Bu tam anlamıyla veri erişim katmanlarında kullanılabilecek bir metod tipidir. Bir saklı yordamı çalıştırırken eğer aldığı giriş parametreleri (input parameter) varsa bunları mutlaka ilgili SqlCommand nesnesinin Parameters koleksiyonuna aynı adlarda olmak şartıyla eklememiz gerekmektedir. Şimdi ilk olarak varsayılan haliyle böyle bir işi nasıl yapacağımızı düşünmeye çalışalım. Bu amaçla Personel tablomuza satır ekleyen aşağıdaki gibi bir saklı yordamımız olduğunu varsayalım.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">CREATE PROCEDURE dbo.PersonelEkle
(
@Adi nvarchar(50)
,@Soyadi nvarchar(50)
,@Maasi money
,@IseGirisTarihi datetime
,@Departmani nvarchar(50)
)
AS
Insert into Personel (Adi,Soyadi,Maasi,IseGirisTarihi,Departmani)
Values (@Adi,@Soyadi,@Maasi,@IseGirisTarihi,@Departmani)
RETURN</pre>
<p>Saklı yordamımız beş adet input parametresi almaktadır. Bu sp' yi çalıştıracak olan bir SqlCommand nesnesi temel olarak aşağıdaki kodda görüldüğü gibi kullanılacaktır. Dikkat ederseniz saklı yordamımız içerisindeki tüm parametreler cmd isimli SqlCommand nesne örneğinin Parameters koleksiyonuna eklenmekte, eklenirkende metoda gelen değerlerini almaktadırlar.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">class Komutlar
{
private string m_ConStr;
public Komutlar(string conStr)
{
m_ConStr = conStr;
}
public void PersonelEkle(string ad,string soyad,decimal maas,DateTime iseGiris,string departman)
{
using (SqlConnection con = new SqlConnection(m_ConStr))
{
SqlCommand cmd = new SqlCommand("PersonelEkle", con);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Adi",ad);
cmd.Parameters.AddWithValue("@Soyadi",soyad);
cmd.Parameters.AddWithValue("@Maasi",maas);
cmd.Parameters.AddWithValue("@IseGirisTarihi",iseGiris);
cmd.Parameters.AddWithValue("@Departmani",departman);
con.Open();
cmd.ExecuteNonQuery();
}
}
}
class Program
{
static void Main(string[] args)
{
Komutlar kmt = new Komutlar("data source=localhost;database=AdventureWorks;integrated security=SSPI");
kmt.PersonelEkle("Hey", "Mayk", 1000, new DateTime(2001, 1, 1), "Spor Arabalar");
}
}</pre>
<p>Bu kod başarılı bir şekilde çalışacaktır. Ancak dikkat ederseniz PersonelEkle metodu sadece PersonelEkle saklı yordamını çalıştırabilir. Dahası, metodun içerisindeki SqlCommand nesne örneğine ait parametre değerleri, aslında metoda gelen parametrelerin değerleridir. Dolayısıla parametrelerin adları hatta tipleri değişebilir. Bu tarz durumlarda sürekli olarak kod üzerinde düzenlemeler yapmamız ve yeniden derlememiz yada başka bir yol düşünmemiz gerekecektir. Oysaki aşağıdaki metod tam anlamıyla her hangibir Sql saklı yordamının her hangi sayıda parametresine hizmet edebilecek niteliktedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public void Execute(string spAdi, params object[] degerler)
{
using (SqlConnection conn = new SqlConnection(m_ConStr))
{
using (SqlCommand cmd = new SqlCommand(spAdi, conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
conn.Open();
// DeriveParameters sadece Sp' lerde işe yarayan bir tekniktir.
SqlCommandBuilder.DeriveParameters(cmd);
for (int i = 1; i <= degerler.Length; i++)
cmd.Parameters[i].Value = degerler[i-1];
cmd.ExecuteNonQuery();
}
}
}</pre>
<p>Metodumuz ilk parametre olarak çalıştıracağı saklı yordamın adını alır. Daha sonra ise <strong>params</strong> anahtar sözcüğünden faydalanarak n sayıda, object tipinden elemanlar taşıyan bir diziyi parametre almaktadır. Metodumuz içerisinde yer alan SqlCommandBuilder sınıfının static <strong>DeriveParameters</strong> metodu ise, parametre olarak aldığı SqlCommand nesnesinin işaret ettiği saklı yordama gider, bu yordamın parametrelerini (ilk parametresi @RETURN_VALUE olacak şekilde) alır ve ilgili komut nesnesinin Parameters koleksiyonuna ekler. Eğer uygulamayı debug ederseniz aşağıdaki ekran görüntüsünde olduğu gibi saklı yordam parametrelerinin cmd nesnesine eklendiğini görebilirsiniz.</p>
<p><img src="/makale/images/mk180_1.gif" alt="" width="416" height="516" border="0" /></p>
<p>Execute metodumuzun bu versiyonunu aşağıdaki gibi test edebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">Komutlar kmt = new Komutlar("data source=localhost;database=AdventureWorks;integrated security=SSPI");
kmt.Execute("PersonelEkle","Hey", "Mayk", 1000, new DateTime(2001, 1, 1), "Spor Arabalar");</pre>
<p>Peki DeriveParameters metodu aslında ne yapmaktadır. <strong>Sql Server Profiler </strong>yardımıyla bu uygulmanın arka planda çalıştırdığı sql kodlarını incelersek aşağıdaki gibi bir çağrı ile karşılaşırız.</p>
<p><img src="/makale/images/mk180_11.gif" alt="" width="519" height="184" border="0" /></p>
<p>Dikkat ederseniz <strong> sp_procedure_params_managed</strong> isimli bir saklı yordam, parametrelerini elde etmek istediğimi saklı yordamın adını parametre alarak çalıştırılmaktadır. Bu ifadeyi Sql Server 2005 Management Studio üzerinde çalıştırırsak, aşağıdaki gibi bir çıktı elde ederiz.</p>
<p><img src="/makale/images/mk180_12.gif" alt="" width="626" height="299" border="0" /></p>
<p>Sistem sp' lerinden olan <strong> sp_procedure_params_managed</strong> aslında PersonelEkle isimli saklı yordam içerisindeki tüm parametreleri ve bu parametrelere ait detaylı bilgileri bir tablo olarak geriye döndürmektedir. Bu tabloda parametrelerin adlarından tutunda veri tiplerine kadar, null değer içerip içermekyeceklerinden taşıyacakları veri uzunluğuna kadar tüm bilgiler yer almaktadır. Bu tabloyu değerlendiren elbette SqlCommandBuilder nesnesinin kendisidir. Tablo içerisindeki bilgilere göre ilgili SqlCommand nesnesinin parameters koleksiyonuna gerekli eklemeler yapılır.</p>
<table id="table147" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td valign="top" width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td><em><strong>OleDbCommandBuilder</strong>, <strong>OracleCommandBuilder</strong>, <strong>ODBCCommandBuilder</strong> sınıflarıda DeriveParameters metodunu destekler. Tek şart, ilgili veri tabanı sisteminin saklı yordama ait parametre yapısını getirebiliyor olmasıdır. Unutmayalım; DeriveParameters sadece saklı yordamlar için geçerlidir. Düz Sql sorgu cümlelerini ele alan komutlar için (parametrik bile olsalar) <strong> InvalidOperationException</strong> istisnası döndürmektedir.</em></td>
</tr>
</tbody>
</table>
<p><strong>2. Çakışma durumları için uygun olan yöntemi ConflictOption özelliği ile belirleyebilme</strong></p>
<p>Bağlantısız katman nesneleri ile çalışırken, başımızı en çok ağrıtan konulardan biriside, birbirlerinden habersiz olarak bir den fazla kullanıcının aynı veri üzerinde değişiklik yapmasıdır. Böyle bir durumda son güncelleme kazansın (Last Wins) tekniğini tercih edebilir yada DbConcurrencyViolation istisnasını ele alabiliriz. <em>(Konu hakkında detaylı bilgi için <a href="http://www.bsenyurt.com/MakaleGoster.aspx?ID=112">tıklayın</a>.)</em> Hangi tekniği seçersek, Update ve Delete sorgularının where koşullarında değişiklik olacaktır.</p>
<p>SqlCommandBuilder sınıfına yeni katılan <strong>ConflictOptions</strong> özelliği ile üretilen komutların bizim seçeceğimiz çakışma kuralına göre oluşturulması sağlanabilir. Last Wins tekniği için Where koşuluna, o tabloda yer alan Primary Key alanın orjinal değeri, <strong>DBConcurrencyViolation</strong> durumda ise tüm alanların orjinal değerleri yada Id alanı ile birlikte varsa TimeStamp gibi alanların orjinal değeleri hesaba katılacaktır. Şimdi bu durumu analiz edeceğimiz örnek bir Windows uygulamasını aşağıdaki gibi geliştirelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public partial class Form1 : Form
{
private SqlConnection conn;
private SqlDataAdapter daPersonel;
private DataTable dtPersonel;
public Form1()
{
InitializeComponent();
}
// DataAdapter hazırlanır.
private void PrepareDataAdapter()
{
conn = new SqlConnection("data source=localhost;database=AdventureWorks;integrated security=SSPI");
daPersonel = new SqlDataAdapter("Select PersonelId,Adi,Soyadi,Maasi,IseGirisTarihi,Departmani,Durum From Personel", conn);
}
/* SqlCommandBuilder hazırlanır ve Update, Delete, Insert komutlarını hazırlaması sağlanır. Oluşan komutlar bilgi amacıyla StringBuilder kullanılarak bir RichTextBox kontrolüne yazılır. */
private void PrepareCommands()
{
SqlCommandBuilder cmb = new SqlCommandBuilder(daPersonel);
ConflictOption selectedOption = (ConflictOption)Enum.Parse(typeof(ConflictOption), cmbConflictOptions.SelectedItem.ToString());
cmb.ConflictOption = selectedOption; // Çakışma seçeneği belirlenir.
daPersonel.InsertCommand = cmb.GetInsertCommand();
daPersonel.UpdateCommand = cmb.GetUpdateCommand();
daPersonel.DeleteCommand = cmb.GetDeleteCommand();
StringBuilder builder = new StringBuilder();
builder.Append("Insert Command : \n");
builder.Append(daPersonel.InsertCommand.CommandText + "\n");
builder.Append("Update Command : \n");
builder.Append(daPersonel.UpdateCommand.CommandText + "\n");
builder.Append("Delete Command : \n");
builder.Append(daPersonel.DeleteCommand.CommandText + "\n");
txtCommands.Text = "";
txtCommands.Text = builder.ToString();
}
/* Select düğmesine basıldığında dataTable oluşturulur, SqlDataAdapter tarafından doldurulur ve DataGridView kontrolüne veri kaynağı olarak bağlanır. */
private void btnSelect_Click(object sender, EventArgs e)
{
dtPersonel = new DataTable();
daPersonel.Fill(dtPersonel);
grdPersonel.DataSource = dtPersonel;
grdPersonel.Columns.Remove("Durum"); // TimeStamp tipi alanlar DataGridView' da problem çıkarttığı için çıkartıldı.
}
/* Form yüklenirken DataAdapter hazırlanır, ConflictOption için enum sabiti üzerinden alınan değerleri ComboBox' a dolduran PrepareConflictOption metodu çağırılır.*/
private void Form1_Load(object sender, EventArgs e)
{
PrepareDataAdapter();
PrepareConflictOptions();
cmbConflictOptions.SelectedIndex = 0;
}
// ConflictOption enum sabiti içerisindeki değerleri ComboBox kontrolüne doldurur.
private void PrepareConflictOptions()
{
string[] conflictOptions = Enum.GetNames(typeof(ConflictOption));
foreach (string option in conflictOptions)
cmbConflictOptions.Items.Add(option);
}
/* DataTable üzerindeki değişiklikeri asıl veri kaynağına yazmadan önce SqlCommandBuilder oluşturulduğu ve Delete, Update, Insert komutlarının hazırlandığı PrepareCommands metodunu çağırır. */
private void btnUpdate_Click(object sender, EventArgs e)
{
PrepareCommands();
daPersonel.Update(dtPersonel);
}
}</pre>
<p>Kod tarafında bizim için önemli olan kısım PrepareCommand metodu içerisinde yaptıklarımızdır. Bu metod içerisinde SqlDataAdapter nesnesi için gerekli Update,Delete ve Insert komutlarını oluşturmaktayız. Çakışma seçeneğini dikkat ederseniz <strong>ConflictOption</strong> enum sabiti üzerinden almaktayız. Uygulamada SqlDataAdapter için gerekli komutları oluşturduktan sonra, Update metodunu belirlediğimiz çakışma seçeneğine göre hazırlanan komutlara göre yürütmekteyiz. ConflictOption enum sabiti <strong>CompareAllSearchableValues</strong>, <strong>CompareRowVersion</strong> ve <strong>OverwriteChanges</strong> olmak üzere üç değer alabilmektedir. Buna göre SqlCommandBuilder nesnesinin bu 3 seçenek için ürettiği çıktılar aşağıdaki gibi olacaktır.</p>
<p><em>CompareAllSearchableValues için;</em></p>
<p><img src="/makale/images/mk180_2.gif" alt="" width="518" height="377" border="0" /></p>
<p>Dikkat ederseniz Update ve Delete sorgularında Where cümleciğine TimeStamp tipindeki Durum alanı hariç tüm alanlar katılmıştır.</p>
<p><em>CompareRowVersion için;</em></p>
<p><img src="/makale/images/mk180_3.gif" alt="" width="518" height="352" border="0" /></p>
<p>Dikkat ederseniz Update ve Delete sorgularında Where koşuluna sadece Primary Key olan PersonelId alanı ve TimeStamp tipinden olan Durum alanı katılmıştır. Bu çeşit bir sorgu özellikle DBConcurrency Violation durum için idealdir.</p>
<table id="table148" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td valign="top" width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td><em>CompareRowVersion özellikle DBConcurrencyViolation durumu için biçilmiş kaftan gibi gözüksede, diğer veri sağlayıcıları için geliştirilmiş CommandBuilder nesnelerinde aynı geçerlilik olmayabilir. Nitekim, <strong>Timestamp veri türü her veritabanı sisteminde var olan bir tür değildir</strong>. Bu tip veri türlerine sahip olmayan sistemlerde mecburen DBConcurrencyViolation durumlarının ele alınmasında, where cümleciğinden sonra mümkün olan tüm alanların hesaba katılması gerekecektir. Bir başka deyişle <strong> CompareAllSearchableValues</strong> seçeneği seçilecektir.</em></td>
</tr>
</tbody>
</table>
<p><em>OverwriteChanges için;</em></p>
<p><img src="/makale/images/mk180_4.gif" alt="" width="518" height="354" border="0" /></p>
<p>Dikkat ederseniz Update ve Delete sorgularına ait Where koşullarına sadece Primary Key olan PersonelId alanı katılmıştır. Dolayısıyla Last Wins modeli geçerlidir.</p>
<p>Görüldüğü gibi, SqlCommandBuilder sınıfına Framework 2.0 ile gelen ConflictOption özelliği, çakışma senaryolarına bağlı olarak uygun Delete ve Update komutlarının hazırlanmasını sağlamaktadır.</p>
<p><strong>3. Update işlemleri sırasında sadece güncellenen parametreleri sql sunucusuna gönderebilme</strong></p>
<p>SqlDataAdapter bağlantısız katman nesnelerinde yapılan değişilikleri asıl veri kaynağına yazmak üzere <strong>Update</strong> metodunu kullanmaktadır. Bağlantısız katmandaki verilerde sadece değişikliğe uğramış alanları update sorgularına dahil etmek istersek SqlCommandBuilder' ın <strong>SetAllValues</strong> isimli özelliğine false değerini atamamız yeterli olacaktır. SetAllValues özelliği varsayılan olarak true değerine sahiptir. Yani bir satırdaki alanların bazılarında değişiklik olmasada update sorgusuna gönderilmektedir. Bu durumu Sql Server Profiler yardımıyla kolayca analiz edebiliriz. Ama öncesinde kodumuzda aşağıdaki değişikliği yapalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void PrepareCommands()
{
SqlCommandBuilder cmb = new SqlCommandBuilder(daPersonel);
ConflictOption selectedOption = (ConflictOption)Enum.Parse(typeof(ConflictOption), cmbConflictOptions.SelectedItem.ToString());
cmb.ConflictOption = selectedOption;
cmb.SetAllValues = false;
daPersonel.InsertCommand = cmb.GetInsertCommand();
daPersonel.UpdateCommand = cmb.GetUpdateCommand();
daPersonel.DeleteCommand = cmb.GetDeleteCommand();
// diğer kod satırları
}</pre>
<p>Şimdi Windows uygulamamızı çalıştırılalım ve satırlar üzerinde, bazı alanlarda (örneğin sadece ad alanında) değişiklik yapıp Update metodunu çalıştıralım.</p>
<p><img src="/makale/images/mk180_5.gif" alt="" width="518" height="109" border="0" /></p>
<p>Update işlemini başlattıktan sonra <strong>Sql Server Profiler</strong> ile sql tarafına gönderilen update sorgularına bakarsak aşağıdaki sonuçları elde ederiz.</p>
<p><img src="/makale/images/mk180_6.gif" alt="" width="598" height="225" border="0" /></p>
<p>Örnek olarak sadece PersonelId değeri 1 olan satırın Adi alanını değiştirdiğimizden ve SqlCommandBuilder nesne örneğinde <strong>SetAllValues</strong> isimli özelliğe <strong>false</strong> değerini atadığımızdan, Update sorgusunda sadece Adi alanı kullanılmıştır. Ancak SetAllValues özelliğini hiç değiştirmessek yada bilinçli olarak true değerini atarsak aşağıdaki sonuçları elde ederiz.</p>
<p><img src="/makale/images/mk180_7.gif" alt="" width="627" height="248" border="0" /></p>
<p>Gördüğünüz gibi bu kez Update sorgusunda bütün alanlar kullanılmıştır.</p>
<p><strong>4. SqlDataAdapter için üretilen command' lerde kolon adı kullanılmasını tercih edebilme</strong></p>
<p>Şu ana kadar incelediğimiz maddelerde SqlCommandBuilder nesnesi, Update, Insert ve Delete komutlarını hazırlarken parametre adları olarak <strong>@P[Rakam]</strong> notasyonunu kullanmıştır. Yine Framework 2.0 ile gelen bir özellik sayesinde <strong>@P[KolonAdı]</strong> notasyonunu kullanma şansınada sahibiz. Bunun için <strong>GetInsertCommand, GetUpdateCommand</strong> ve <strong>GetDeleteCommand</strong> isimli metodların aşırı yüklenmiş versiyonunlarını kullanmamız gerekiyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">// Insert versiyonu
public SqlCommand GetInsertCommand (bool useColumnsForParameterNames)
// Delete versiyonu
public SqlCommand GetDeleteCommand (bool useColumnsForParameterNames)
// Update versiyonu
public SqlCommand GetUpdateCommand (bool useColumnsForParameterNames)</pre>
<p>Bu versiyon bool tipinden bir değer almakta olup, parametre adlarında kolon isimlerinin gösterilip gösterilmeyeceğini belirtmektedir. Örnek uygulamamızda basit olarak bu bool değişkeni tutacak bir CheckBox kontrolü ele alıyoruz. Metodumuzu ise aşağıdaki gibi değiştirmemiz yeterli olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void PrepareCommands()
{
SqlCommandBuilder cmb = new SqlCommandBuilder(daPersonel);
ConflictOption selectedOption = (ConflictOption)Enum.Parse(typeof(ConflictOption), cmbConflictOptions.SelectedItem.ToString());
cmb.ConflictOption = selectedOption;
cmb.SetAllValues = false;
bool useParameterNames = chkUseParameter.Checked;
daPersonel.InsertCommand = cmb.GetInsertCommand(useParameterNames);
daPersonel.UpdateCommand = cmb.GetUpdateCommand(useParameterNames);
daPersonel.DeleteCommand = cmb.GetDeleteCommand(useParameterNames);
// diğer kod satırları
}</pre>
<p>Şimdi uygulamamızı yeniden çalıştırıp farklı çakışma tipleri için oluşan sorgulara bakarsak aşağıdaki sonuçları elde ederiz.</p>
<p><em>CompareAllSearchableValues için;</em></p>
<p><img src="/makale/images/mk180_8.gif" alt="" width="497" height="242" border="0" /></p>
<p><em>CompareRowVersion için;</em></p>
<p><img src="/makale/images/mk180_9.gif" alt="" width="517" height="210" border="0" /></p>
<p><em>OverwriteChanges için;</em></p>
<p><img src="/makale/images/mk180_10.gif" alt="" width="472" height="181" border="0" /></p>
<p>Gördüğünüz gibi parametre adları artık kolon adlarından oluşturulmaktadır.</p>
<p>SqlCommandBuilder nesnesi Framework 2.0 daki ek fonksiyonellikleri ve özellikleri sayesinde artık daha kullanışlı hale gelmiştir. Diğer CommandBuilder nesneleride benzer işlevsellikleri sağlamakla birlikte, kullanılan veritabanı sisteminin sahip olduğu imkanlarda önemlidir. Örneğin OleDbCommandBuilder sınıfınında DeriveParameters metodu vardır ve bir OleDb kaynaklarından sp desteği olmayan veritabanlarına bağlanabilmemiz de mümkündür. Sp desteği olmadığı için böyle bir durumda OleDbCommandBuilder' ın DeriveParameters fonksiyonu bir işe yaramayacaktır. Diğer yandan özellikle bağlantısız katman nesneleri ile çalışırken çeşitli çakışma kritlerlerine göre otomatik olarak sorguların oluşturulabilmesi önemli bir özellik olarak karşımıza çıkmaktadır. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/makale/images/CommandBuilder.rar">Örnek kod için tıklayın.</a></p>2006-11-13T12:00:00+00:00ado.netsqlcommandbuilderbsenyurtSqlCommandBuilder sınıfı özellikle bağlantısız katman (disconnected layer) modelinde sıkça kullanılmaktadır. Çoğunlukla, SqlDataAdapter tipine ait nesneler için gerekli olan UpdateCommand, InsertCommand ve DeleteCommand özelliklerine bağlı SqlCommand nesnelerini sıfırdan oluşturmamak için tercih edilebilir...https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=5255d71e-bad5-4571-b03f-eba9c8461be20https://buraksenyurt.com/trackback.axd?id=5255d71e-bad5-4571-b03f-eba9c8461be2https://buraksenyurt.com/post/SqlCommandBuilder-icin-4-Tavsiye-bsenyurt-com-dan#commenthttps://buraksenyurt.com/syndication.axd?post=5255d71e-bad5-4571-b03f-eba9c8461be2https://buraksenyurt.com/post/DataReader-Nesnelerini-Kullanc4b1rkene280a6-bsenyurt-com-danDataReader Nesnelerini Kullanırken…2005-04-04T12:00:00+00:00bsenyurt<p>Değerli Okurlarım, Merhabalar.</p>
<p>Bir önceki makalemizde Command nesnelerini kullanırken dikkat etmemiz gereken noktalara değinmiştik. Bu makalemizde ise DataReader nesnelerini kullanırken bizlere avantaj sağlayacak tekniklere değinmeye çalışacağız. Önceki makalemizde olduğu gibi ağırlık olarak SqlDataReader nesnesini ve Sql veritanını kullanacağız. DataReader nesneleri bildiğiniz gibi, bağlantılı katman (connected-layer) üzerinde çalışmaktadır. Görevleri veri kaynağından, uygulama ortamına doğru belli bir akım üzerinden hareket edecek veri parçalarının taşınmasını sağlamaktır.</p>
<p>DataReader nesneleri ile veri almak bağlantısız katman (disconnected-layer) nesnelerine veri çekmekten çok daha hızlıdır. Çoğunlukla DataReader nesnelerinin kullanılmasının tercih edileceği durumlar vardır. Uygulamalarımız geliştirirken çoğu zaman bağlantılı katman ile bağlantısız katman nesneleri arasında seçim yapmakta zorlanırız. Aşağıdaki tablo <em>"Ne zaman DataReader kullanırız?" </em>sorusuna ışık tutan noktalara değinmektedir.</p>
<table id="table1" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td>Eğer Windows veya Asp.Net uygulaması geliştiriyor ve birden fazla form(page) için veri bağlama (data-binding) <span style="text-decoration: underline;"><strong><em>gerçekleştirmiyorsanız</em></strong>,</span></td>
</tr>
<tr>
<td>Veriyi ara belleğe alma (caching) <span style="text-decoration: underline;"> <em><strong>ihtiyacınız yok ise</strong></em>.</span></td>
</tr>
<tr>
<td>Eğer tablolarınız arasındaki ilişkileri (relations) uygulamalarınızda <span style="text-decoration: underline;"><em><strong>kullanmıyorsanız</strong></em>.</span></td>
</tr>
<tr>
<td bgcolor="#cccccc"><strong>DataReader Kullanmayı Tercih Edin.</strong></td>
</tr>
</tbody>
</table>
<p><br />Gelelim DataReader nesnelerini kullanırken dikkat edeceğimiz altın noktalara. Bu teknikler uygulamalarımızın performansını arttıracak nitelikte olup aşağıdaki tabloda belirtilmektedir.</p>
<table id="table2" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td>DataReader nesnelerini kullanırken açık Connection' ların kapatılmasını unutmayın.</td>
</tr>
<tr>
<td>Sorgu sonucu sadece tek bir satır döneceği kesin ise SingleRow tekniğini kullanın.</td>
</tr>
<tr>
<td>Toplu sorgular (Batch Queries) için Next Result tekniğini kullanın.</td>
</tr>
<tr>
<td bgcolor="#ffffff">Binary veya Text bazlı alan verilerini okurken SequentialAccess tekniğini kullanın.</td>
</tr>
</tbody>
</table>
<p><br />Şimdi bu teknikleri birer birer inceleyelim.</p>
<p><strong>Açık Connection' ları Kapatmayı Unutmamak İçin</strong></p>
<p>DataReader nesneleri açık ve geçerli bir Connection nesnesine ihtiyaç duyarlar. Lakin aynı Connection' ı kullanan DataReader nesneleri söz konusu ise, her bir DataReader' ın kullanılabilmesi için bir önceki DataReader' ın kullandığı Connection nesnesinin kapatılmış olması gerekir. (Bu aynı Connection nesnesini kullanan DataReader' lar var ise geçerlidir.) Örneğin aşağıdaki uygulama kodunu ele alalım;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
namespace DataReaderDikkat
{
public class DbWork
{
private SqlConnection con;
private SqlDataReader dr;
/* CloseConnection kullanımına örnek metod.*/
public DbWork(string conStr)
{
con=new SqlConnection(conStr);
}
public SqlDataReader Results(string selectQuery)
{
SqlCommand cmd=new SqlCommand(selectQuery,con);
con.Open();
dr=cmd.ExecuteReader(); // Hatalı kullanım.
return dr;
}
}
}</pre>
<p>DbWork sınıfımız constructor metodunda bir SqlConnection nesnesi oluşturur. Results isimli metodumuz ise parametre olarak aldığı sorgu sonucu bir SqlDataReader nesnesi ile geri döndürür. Şimdi bu sınıfı kullanan aşağıdaki uygulamamızı ele alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
namespace DataReaderDikkat
{
class ClosingDataReader
{
[STAThread]
static void Main(string[] args)
{
DbWork worker=new DbWork("data source=BURKI;database=Northwind;integrated security=SSPI");
#region CloseConnection Kullanın.
SqlDataReader drOrders=worker.Results("SELECT TOP 10 * FROM Orders");
while(drOrders.Read())
{
Console.WriteLine(drOrders[0].ToString()+" "+drOrders[1].ToString());
}
drOrders.Close();
SqlDataReader drCustomers=worker.Results("SELECT TOP 10 * FROM Customers");
while(drCustomers.Read())
{
Console.WriteLine(drCustomers[0].ToString()+" "+drCustomers[1].ToString());
}
drCustomers.Close();
#endregion
}
}
}</pre>
<p>Uygulamamızı bu haliyle çalıştırdığımızda aşağıdaki istisnayı alırız.</p>
<p><img src="/makale/images/mk119_2.gif" alt="" width="638" height="252" border="0" /></p>
<p>Sebep ilk drOrders SqlDataReader nesnesinin kullandığı SqlConnection nesnesinin kapatılmamış olması ve bağlantının halen daha açık olarak kalmasıdır. Özellikle yukarıdaki gibi iş nesneleri üzerinden yürütülen sorgularda Connection nesnelerinin otomatik olarak kapatılmasını sağlamak için CommandBehavior numaralandırıcısının CloseConnection değerini kullanmayı unutmamak gerekir. Dolayısıyla DbWork sınıfımızdaki Results metodunu aşağıdaki gibi düzenlersek istediğimiz sonucu elde eder ve istisnanın üstesinden geliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public SqlDataReader Results(string selectQuery)
{
SqlCommand cmd=new SqlCommand(selectQuery,con);
con.Open();
dr=cmd.ExecuteReader(CommandBehavior.CloseConnection); // Doğru Kullanım.
// dr=cmd.ExecuteReader(); // Hatalı kullanım.
return dr;
}</pre>
<p><img src="/makale/images/mk119_3.gif" alt="" width="388" height="323" border="0" /></p>
<p><strong>Sorgu Sonucu Tek Bir Satır Döndüğü Kesin İse</strong></p>
<p>Bazı durumlarda tablolardan dönen satır sayısının 1 olacağı kesindir. Bu satırlar çoğunlukla belirli key alanı üzerinden elde edilen parametrik sorguların sonucudur. Örneğin, benzersiz değer alan (unique), ve otomatik olarak artan alanların parametre olarak kullanıldığı sorgular göz önüne alabiliriz. Bu tarz sorgularda, bağlantısız katman nesnelerini kullanmak gereksiz yere kaynak tüketimine neden olacaktır. Böyle bir durumda DataReader nesneleri bağlantısız katman nesnelerine oranla çok daha performanslı ve hızlı çalışacaktır. Burada önemli olan DataReader ile dönen satırı okumak için ilgili Command nesnesinin ExecuteReader metoduna verilecek CommandBehavior numaralandırıcısının değeridir. Aşağıda bu tekniğin kullanımına bir örnek verilmiştir. Veritabanı işlemlerimizi topladığımız DbWork sınıfı basit olarak constructor metodu ile bir SqlConnection nesnesi örneklendirir. GetRow metodumuz ise gelen sorguya ve parametre değerine göre bulunan satırı okuyacak bir SqlDataReader nesnesini geriye döndürür.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
namespace DataReaderDikkat
{
public class DbWork
{
private SqlConnection con;
private SqlDataReader dr;
/* CloseConnection kullanımına örnek metod.*/
public DbWork(string conStr)
{
con=new SqlConnection(conStr);
}
public SqlDataReader GetRow(string FindQuery,int orderID)
{
SqlCommand cmd=new SqlCommand(FindQuery,con);
cmd.Parameters.Add("@OrderID",SqlDbType.Int);
cmd.Parameters["@OrderID"].Value=orderID;
con.Open();
dr=cmd.ExecuteReader((CommandBehavior)40);
return dr;
}
}
}</pre>
<p>Burada ExecuteReader metodunun kullanılışına dikkatinizi çekerim. CommandBehavior numaralandırıcısının alacağı değerlerin sayısal karşılıkları vardır. SingleRow değerinin integer karşılığı 8, CloseConnection numaralandırıcısının integer karşılığı ise 32' dir. Dolayısıyla (CommandBehavior)40, oluşturulan SqlDataReader nesnesine sadece tek bir satır döndüreceğini ve SqlDataReader nesnesi kapatıldığında SqlConnection nesnesinin de otomatik olarak kapatılacağını belirtir. Gelelim ana program kodlarımıza;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
namespace DataReaderDikkat
{
class ClosingDataReader
{
[STAThread]
static void Main(string[] args)
{
DbWork worker=new DbWork("data source=BURKI;database=Northwind;integrated security=SSPI");
#region SingleRow Kullanın.
SqlDataReader drFind=worker.GetRow("SELECT * FROM Orders WHERE OrderID=@OrderID",10248);
drFind.Read();
Console.WriteLine("BULUNAN SATIR "+drFind[0].ToString());
drFind.Close();
drFind=worker.GetRow("SELECT * FROM Orders WHERE OrderID=@OrderID",10249);
drFind.Read();
Console.WriteLine("BULUNAN SATIR "+drFind[0].ToString());
drFind.Close();
#endregion
}
}
}</pre>
<p><img src="/makale/images/mk119_1.gif" alt="" width="316" height="107" border="0" /></p>
<p><strong>Toplu Sorgula İçin DataReader Kullanın</strong></p>
<p>Özellikle birden fazla sonuç kümesini (result set) almak istiyorsanız ve DataReader kullanmaya karar verdiyseniz en uygun yöntem NextResult metodunun uygulanmasıdır. Gerçek şu ki, böyle bir durumda birden fazla DataReader nesnesi peş peşe çalıştırılabilir. Aynı bu makalemizdeki ilk örneğimizde olduğu gibi. Eğer bu tarz sonuç kümelerini gerçekten arka arkaya alıyorsak ve aynı Connection' ı kullanıyorsak, birden fazla DataReader nesnesi kullandığımız için aynı Connection' ı kullanıyor olsakta veritabanına doğru birden fazla sayıda tur atmış oluruz. Çünkü her bir DataReader nesnesinden sonradan gelen DataReader nesnelerinin aynı Connection' ı kullanmalarına imkan sağlamamız için ilgili Connection' ları kapatmak gibi bir zorunluluğumuz vardır.</p>
<p>Oysaki bu sorguları Batch Query olarak hazırlarsak tek bir DataReader nesnesi ve tek bir Connection ile daha hızlı sonuç elde edebiliriz. Aynı aşağıdaki örnekte olduğu gibi. DbWork sınıfımıza bu sefer, Batch Query çalıştıracak bir metot ekledik. Metodumuz gelen string dizisi içindeki sorgu cümlelerini alıp bir StringBuilder yardımıyla birleştiriyor. Bu işlemin sonucu olarak "sorgu cümlesi 1;sorgu cümlesi 2;sorgu cümlesi 3;" tarzında bir query string' i oluşturuyoruz ki bu bizim Batch Qeury' mizdir. Daha sonra ilgili sorgu topluluğunu çalıştıracak bir SqlCommand nesnesi oluşturuyor ve bu komutu yürüterek elde ettiğimiz SqlDataReader nesnesini geriye döndürüyoruz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace DataReaderDikkat
{
public class DbWork
{
private SqlConnection con;
private SqlDataReader dr;
/* CloseConnection kullanımına örnek metod.*/
public DbWork(string conStr)
{
con=new SqlConnection(conStr);
}
public SqlDataReader BatchResults(string[] sorgular)
{
StringBuilder sbSorgular=new StringBuilder();
for(int i=0;i<sorgular.Length;i++)
{
sbSorgular.Append(sorgular[i]+";");
}
SqlCommand cmd=new SqlCommand(sbSorgular.ToString(),con);
con.Open();
SqlDataReader dr=cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dr;
}
}
}</pre>
<p>Gelelim uygulama kodlarımıza;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data;
using System.Data.SqlClient;
namespace DataReaderDikkat
{
class ClosingDataReader
{
[STAThread]
static void Main(string[] args)
{
DbWork worker=new DbWork("data source=BURKI;database=Northwind;integrated security=SSPI");
#region BatchQuery' lerde
string[] sorguKumesi={"SELECT TOP 5 * FROM Orders","SELECT TOP 5 * FROM Customers","SELECT TOP 5 * FROM [Order Details]"};
SqlDataReader drToplu=worker.BatchResults(sorguKumesi);
do
{
while(drToplu.Read())
{
Console.WriteLine(drToplu[0].ToString()+" "+drToplu[1].ToString());
}
Console.WriteLine("------------");
}while(drToplu.NextResult());
drToplu.Close();
#endregion
}
}
}</pre>
<p><img src="/makale/images/mk119_4.gif" alt="" width="388" height="323" border="0" /></p>
<p><strong>Binary ve Text Tipindeki Alanları Okurken</strong></p>
<p>Bazı tablolar içerisinde text veya binary tabanlı alanlar tutarız. Örneğin resim dosyalarının tablolarda binary olarak saklanması veya makalelerin html verisinin text tipli alanlar olarak saklanması gibi. Özellikle bu tarz alanları okurken DataReader nesnelerini kullanıyorsak, SequentialAccess tekniğini kullanmak bize avantaj sağlayabilir. Öyle ki bu tekniği uyguladığımızda ilgili satırın tamamı okunacağına bunun yerine bir stream oluşturulur. Siz bu stream' i kullanarak ilgili alana ait binary yada text veriyi okursunuz. Örneğin aşağıdaki kodlar ile Northwind database' inde yer alan Categories tablosundaki Text tipindeki Description alanının ilk 150 karakteri okunmaktadır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public SqlDataReader ReadText(string Query)
{
SqlCommand cmd=new SqlCommand(Query,con);
con.Open();
dr=cmd.ExecuteReader((CommandBehavior)48); // Bu kez hem SequentialAccess hem de CloseConnection seçili.
return dr;
}</pre>
<p>Metodumuzun kullanılışı ise aşağıdaki gibi olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlDataReader drText=worker.ReadText("SELECT CategoryName,Description,Picture FROM Categories");
char[] tampon=new char[150];
while(drText.Read())
{
drText.GetChars(1,0,tampon,0,150);
for(int i=0;i<150;i++)
{
Console.Write(tampon[i].ToString());
}
Console.WriteLine();
}</pre>
<p><img src="/makale/images/mk119_5.gif" alt="" width="516" height="263" border="0" /></p>
<p>Bu makalemizde DataReader nesnelerini kullanırken dikkat edeceğimiz ve bize avantaj sağlayacak teknikleri incelemeye çalıştık. Bir sonraki makalemizde görüşmek dileğiyle hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/makale/images/DataReaderDikkat.zip">Örnek için tıklayın.</a></p>2005-04-04T12:00:00+00:00ado.netsqldatareaderbsenyurtBir önceki makalemizde Command nesnelerini kullanırken dikkat etmemiz gereken noktalara değinmiştik. Bu makalemizde ise DataReader nesnelerini kullanırken bizlere avantaj sağlayacak tekniklere değinmeye çalışacağız. Önceki makalemizde olduğu gibi ağırlık olarak SqlDataReader nesnesini ve Sql veritanını kullanacağız. DataReader nesneleri bildiğiniz gibi, bağlantılı katman (connected-layer) üzerinde çalışmaktadır.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=413d42a7-d4a3-4183-ad38-af246473848f3https://buraksenyurt.com/trackback.axd?id=413d42a7-d4a3-4183-ad38-af246473848fhttps://buraksenyurt.com/post/DataReader-Nesnelerini-Kullanc4b1rkene280a6-bsenyurt-com-dan#commenthttps://buraksenyurt.com/syndication.axd?post=413d42a7-d4a3-4183-ad38-af246473848fhttps://buraksenyurt.com/post/Command-Nesnelerine-Dikkat!-bsenyurt-com-danCommand Nesnelerine Dikkat!2005-03-27T09:00:00+00:00bsenyurt<p>Değerli Okurlarım, Merhabalar.</p>
<p>Bu makalemizde, Command nesnelerini kullanırken performans arttırıcı, kod okunurluğunu kolaylaştırıcı, güvenlik riskini azaltıcı etkenler üzerinde duracağız ve bu kazanımlar için gerekli teknikleri göreceğiz. Örneklerimizi SqlCommand sınıfına ait nesneler üzerinden geliştireceğiz. Bildiğiniz gibi Command nesneleri yardımıyla veritabanına doğru yürütmek istediğimiz sorguları çalıştırmaktayız. Bu sorgular basit Select, Insert, Update, Delete sorguları olabileceği gibi saklı yordamlar (Stored Procedures) veya tablolarda olabilir.</p>
<p>Command nesneleri ayrıca diğer Ado.Net nesnelerinin işletilmelerinde de etkin rol oynamaktadır. Örneğin bağlantısız katman (disconnected layer) nesnelerinin doldurulması veya güncellenmesi için kullanılan DataAdapter nesneleri veya bağlantılı katman (connected layer) üzerinde çalışan DataReader nesneleri gibi. Dolayısıyla Command nesnelerine bağımlı olarak çalışan programların performans, güvenlik ve kod okunurluğu yönünden uygulaması tavsiye edilen bazı teknikler vardır. Command nesnelerinin hazırlanışı ve kullanılması sırasında dikkat etmemiz gereken noktalar aşağıdaki dört madde ile özetlenmiştir.</p>
<table id="table1" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td bgcolor="#cccccc"><strong>SqlCommand Nesneleri İçin Pozitif Yaklaşımlar</strong></td>
</tr>
<tr>
<td>Parametrik sorguların kullanımı.</td>
</tr>
<tr>
<td>Sorguların yeniden kullanım için hazırlanması (Prepare Tekniği)</td>
</tr>
<tr>
<td>En etkin constructor ile nesne örneğinin oluşturulması.</td>
</tr>
<tr>
<td>Tek değerlik dönüşler için ExecuteScalar metodunun tercih edilmesi.</td>
</tr>
</tbody>
</table>
<p><br />Şimdi bu maddelerimizi tek tek incelemeye başlayalım.</p>
<p><em><strong>Parametrik Sorguların Kullanımı.</strong></em></p>
<p>Parametrik sorgular diğer türlerine göre daha hızlı çalışır. Ayrıca Sql Injection' a karşı daha yüksek güvenlik sağlar. Son olarak, parametrik sorgularda örneğin string değerler için tek tırnak kullanma zorunluluğunda kalmazsınız ki bu kodunuzun okunulabilirliğini arttırır. Örneğin aşağıdaki kod parçasını inceleyelim. Bu örneğimizde tabloya veri girişi için INSERT sorgusu kullanılıyor. Sorgumuza ait Sql cümleciğine dikkat edecek olursanız TextBox kontrollerinden değerler almakta. İfade okunurluk açısından oldukça zorlayıcı. Ayrıca tek tırnak kullanılması gerektiğinden yazımında büyük dikkat gerektiriyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlConnection con=new SqlConnection("data source=BURKI;database=Work;integrated security=SSPI");
string insertText="INSERT INTO Maaslar (ADSOYAD,DOGUMTARIHI,MAAS) VALUES ('"+txtADSOYAD.Text.ToString()+"','"+txtDOGUMTARIHI.Text.ToString()+"',"+txtMAAS.Text.ToString()+")";
SqlCommand cmd=new SqlCommand(insertText,con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();</pre>
<p>Oysaki bu tip bir kullanım yerine sorguya giren dış değerleri parametrik olarak tanımlamak çok daha avantajlıdır. Sürat, kolay okunurluk, tek tırnak' dan bağımsız olmak. Aynı kod parçasını şimdi aşağıdaki gibi değiştirelim. Bu sefer sorgumuza alacağımız değerleri SqlCommand nesnesine birer parametre olarak ekledik. ADSOYAD alanımız nvarchar, DOGUMTARIHI alanımız DateTime, MAAS alanımız ise Money tipindedir. Bu nedenle parametrelerde uygun SqlDbType değerlerini seçtik. Daha sonra parametrelerimiz için gerekli değerlerimizi Form üzerindeki kontrollerimizden alıyor. Dikkat ederseniz textBox kontrollerinden veri alırken herhangi bir tür dönüşümü işlemi uygulamadık.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">/* Connection oluşturulur. */
SqlConnection con=new SqlConnection("data source=BURKI;database=Work;integrated security=SSPI");
/* Sorgu cümleciği oluşturulur.*/
string insertText="INSERT INTO Maaslar (ADSOYAD,DOGUMTARIHI,MAAS) VALUES (@ADSOYAD,@DOGUMTARIHI,@MAAS)";
/* Command nesnesi oluşturulur */
SqlCommand cmd=new SqlCommand(insertText,con);
/* Komut için gerekli parametreler tanımlanır. */
cmd.Parameters.Add("@ADSOYAD",SqlDbType.NVarChar,50);
cmd.Parameters.Add("@DOGUMTARIHI",SqlDbType.DateTime);
cmd.Parameters.Add("@MAAS",SqlDbType.Money);
/* Parametre değerleri verilir. */
cmd.Parameters["@ADSOYAD"].Value=txtADSOYAD.Text;
cmd.Parameters["@DOGUMTARIHI"].Value=txtDOGUMTARIHI.Text;
cmd.Parameters["@MAAS"].Value=txtMAAS.Text;
/* Bağlantı açılır komut çalıştırılır ve bağlantı kapatılır. */
con.Open();
cmd.ExecuteNonQuery();
con.Close();</pre>
<p><em><strong>Sorguların Yeniden Kullanım için Hazırlanması (Prepare Tekniği)</strong></em></p>
<p>Stored Procedure' lerin hızlı olmalarının en büyük nedeni sql sorgularına ait planlarının ara bellekte tutulmasıdır. Aynı işlevselliği uygulamalarımızda sık kullanılan sorgu cümleleri içinde gerçekleyebiliriz. Bunun için SqlCommand nesnesinin Prepare metodu kullanılır. Bu metod yardımıyla ilgili sql sorgusuna ait planın Sql sunucusu için ara bellekte tutulması sağlanmış olur. Böylece sorgunun ilk çalıştırılışından sonraki yürütmelerin daha hızlı olması sağlanmış olur. Aşağıdaki kod parçasını ele alalım. Bu sefer arka arkaya 3 satır girişi işlemini gerçekleştiriyoruz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlConnection con=new SqlConnection("data source=BURKI;database=Work;integrated security=SSPI");
string insertText="INSERT INTO Maaslar (ADSOYAD,DOGUMTARIHI,MAAS) VALUES (@ADSOYAD,@DOGUMTARIHI,@MAAS)"; con.Open();
SqlCommand cmd=new SqlCommand(insertText,con);
cmd.Parameters.Add("@ADSOYAD",SqlDbType.NVarChar,50);
cmd.Parameters.Add("@DOGUMTARIHI",SqlDbType.DateTime);
cmd.Parameters.Add("@MAAS",SqlDbType.Money);
// İlk veri girişi
cmd.Parameters["@ADSOYAD"].Value="Burak";
cmd.Parameters["@DOGUMTARIHI"].Value="12.04.1976";
cmd.Parameters["@MAAS"].Value=1000;
cmd.ExecuteNonQuery();
// İkinci veri girişi
cmd.Parameters["@ADSOYAD"].Value="Bili";
cmd.Parameters["@DOGUMTARIHI"].Value="10.04.1965";
cmd.ExecuteNonQuery();
// Üçüncü veri girişi
cmd.Parameters["@ADSOYAD"].Value="Ali";
cmd.Parameters["@DOGUMTARIHI"].Value="09.04.1980";
cmd.ExecuteNonQuery();
con.Close();</pre>
<p>Bu tarz bir kullanım yerine, aşağıdaki kullanım özellikle ağ ortamında işletilecek olan sorgulamalarda daha yüksek performans sağlayacaktır. Tek yapmamız gereken SqlCommand nesnesini ilk kez Execute edilmeden önce Prepare metodu ile Sql Sunucusu için ara belleğe aldırmaktır. Yani;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlConnection con=new SqlConnection("data source=BURKI;database=Work;integrated security=SSPI");
string insertText="INSERT INTO Maaslar (ADSOYAD,DOGUMTARIHI,MAAS) VALUES (@ADSOYAD,@DOGUMTARIHI,@MAAS)"; con.Open();
SqlCommand cmd=new SqlCommand(insertText,con);
cmd.Parameters.Add("@ADSOYAD",SqlDbType.NVarChar,50);
cmd.Parameters.Add("@DOGUMTARIHI",SqlDbType.DateTime);
cmd.Parameters.Add("@MAAS",SqlDbType.Money);
// İlk veri girişi
cmd.Parameters["@ADSOYAD"].Value="Burak";
cmd.Parameters["@DOGUMTARIHI"].Value="12.04.1976";
cmd.Parameters["@MAAS"].Value=1000;
cmd.Prepare();
cmd.ExecuteNonQuery();
// İkinci veri girişi
cmd.Parameters["@ADSOYAD"].Value="Bili";
cmd.Parameters["@DOGUMTARIHI"].Value="10.04.1965";
cmd.ExecuteNonQuery();
// Üçüncü veri girişi
cmd.Parameters["@ADSOYAD"].Value="Ali";
cmd.Parameters["@DOGUMTARIHI"].Value="09.04.1980";
cmd.ExecuteNonQuery();
con.Close();</pre>
<p><em><strong>En Etkin Constructor ile Nesne Örneğinin Oluşturulması.</strong></em></p>
<p>Bir SqlCommand nesnesinin oluşturulması sırasında kullanılacak Constructor metodun seçimi özellikle kod okunurluğu açısından önemlidir. Örneğin aşağıdaki kod kullanımını ele alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">// ConnectionString tanımlanır.
string conStr="data source=BURKI;database=Work;integrated security=SSPI";
// Select sorgu cümlesi tanımlanır.
string selectText="SELECT * FROM Ogrenciler";
// SqlConnection nesnesi oluşturulur.
SqlConnection con=new SqlConnection();
// SqlConnection nesnesi için Connection String atanır.
con.ConnectionString=conStr;
// Connection açılır.
con.Open();
// Yeni bir SqlTransaction nesnesi başlatılır.
SqlTransaction trans=con.BeginTransaction();
// SqlCommand nesnesi tanımlanır.
SqlCommand cmd=new SqlCommand();
// SqlCommand nesnesinin kullanacağı SqlConnection belirlenir.
cmd.Connection=con;
// SqlCommand nesnesinin kullanacağı SqlTransaction belirlenir.
cmd.Transaction=trans;
// SqlCommand nesnesinin yürüteceği sorgu cümlesi belirlenir.
cmd.CommandText=selectText;</pre>
<p>Bu kod aşağıdaki gibi daha etkin bir biçimde yazılabilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">// ConnectionString tanımlanır.
string conStr="data source=BURKI;database=Work;integrated security=SSPI";
// Select sorgu cümlesi tanımlanır.
string selectText="SELECT * FROM Ogrenciler";
// SqlConnection nesnesi oluşturulur ve açılır.
SqlConnection con=new SqlConnection(conStr);
con.Open();
// Yeni bir SqlTransaction nesnesi başlatılır.
SqlTransaction trans=con.BeginTransaction();
// SqlCommand nesnesi tanımlanır.
SqlCommand cmd=new SqlCommand(selectText,con,trans);</pre>
<p><em><strong>Tek Değerlik Dönüşler için ExecuteScalar Metodunun Tercih Edilmesi.</strong></em></p>
<p>Bazen sorgularımızda Aggregate fonksiyonlarını kullanırız. Örneğin bir tablodaki satır sayısının öğrenilmesi için Count fonksiyonunun kullanılması veya belli bir alandaki değerlerin ortalamasının hesaplanması için AVG (ortalama) fonksiyonu vb. Aggregate fonksiyonlarının kullanıldığı durumlarda iki alternatifimiz vardır. Bu alternatiflerden birisi aşağıdaki gibi SqlDataReader nesnesinin ilgili SqlCommand nesnesi ile birlikte kullanılmasıdır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">// ConnectionString tanımlanır.
string conStr="data source=BURKI;database=Work;integrated security=SSPI";
// Select sorgu cümlesi tanımlanır.
string selectText="SELECT COUNT(*) FROM Ogrenciler";
// SqlConnection nesnesi oluşturulur ve açılır.
SqlConnection con=new SqlConnection(conStr);
con.Open();
// SqlCommand nesnesi tanımlanır.
SqlCommand cmd=new SqlCommand(selectText,con);
// SqlDataReader nesnesi satır sayısını almak amacıyla oluşturulur.
SqlDataReader dr=cmd.ExecuteReader(CommandBehavior.SingleResult);
// Elde edilen sonuç okunur.
dr.Read();
// Hücre değeri ekrana yazdırılır.
Console.WriteLine("Öğrenci sayısı "+dr[0].ToString());
// SqlDataReader ve SqlConnection kaynakları kapatılır.
dr.Close();
con.Close();</pre>
<p>Bu teknikte aggregate fonksiyonun çalıştırılmasından dönen değeri elde edebilmek için SqlDataReader nesnesi kullanılmıştır. Ancak SqlCommand nesnesinin bu iş için tasarlanmış olan ExecuteScalar metodu yukarıdaki tekniğe göre daha yüksek bir performans sağlamaktadır. Çünkü çalıştırılması sırasında bir SqlDataReader nesnesine ihtiyaç duymaz. Bu da SqlDataReader nesnesinin kullanmak için harcadığı sistem kaynaklarının var olmaması anlamına gelmektedir. Dolayısıyla yukarıdaki örnekteki kodları aşağıdaki gibi kullanmak daha etkilidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">// ConnectionString tanımlanır.
string conStr="data source=BURKI;database=Work;integrated security=SSPI";
// Select sorgu cümlesi tanımlanır.
string selectText="SELECT COUNT(*) FROM Ogrenciler";
// SqlConnection nesnesi oluşturulur ve açılır.
SqlConnection con=new SqlConnection(conStr);
con.Open();
// SqlCommand nesnesi tanımlanır ve ExecuteScalar ile sonuç anında elde edilir.
SqlCommand cmd=new SqlCommand(selectText,con);
Console.WriteLine("Satır sayısı "+cmd.ExecuteScalar().ToString());</pre>
<p>Bu makalemizde, Command nesnelerini kullanırken bize performans, hız, güvenlik kod okunurluğu açısından avantajlar sağlayacak teknikleri incelemeye çalıştık. Bir sonraki makalemizde görüşmek dileğiyle hepinize mutlu günler dilerim.</p>2005-03-27T09:00:00+00:00ado.netsqlcommandbsenyurtBu makalemizde, Command nesnelerini kullanırken performans arttırıcı, kod okunurluğunu kolaylaştırıcı, güvenlik riskini azaltıcı etkenler üzerinde duracağız ve bu kazanımlar için gerekli teknikleri göreceğiz. Örneklerimizi SqlCommand sınıfına ait nesneler üzerinden geliştireceğiz. Bildiğiniz gibi Command nesneleri yardımıyla veritabanına doğru yürütmek istediğimiz sorguları çalıştırmaktayız. Bu sorgular basit Select, Insert, Update, Delete sorguları olabileceği gibi saklı yordamlar (Stored Procedures) veya tablolarda olabilir.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=79056bac-b574-4553-861f-c55f79a63c9e1https://buraksenyurt.com/trackback.axd?id=79056bac-b574-4553-861f-c55f79a63c9ehttps://buraksenyurt.com/post/Command-Nesnelerine-Dikkat!-bsenyurt-com-dan#commenthttps://buraksenyurt.com/syndication.axd?post=79056bac-b574-4553-861f-c55f79a63c9ehttps://buraksenyurt.com/post/Self-Referencing-Relations-ve-Recursive(Yinelemeli)-Metodlar-bsenyurt-com-danSelf Referencing Relations ve Recursive(Yinelemeli) Metodlar2005-03-14T12:00:00+00:00bsenyurt<p>Çoğumuz çalıştığımız projelerde müdür,müdür yardımcısı gibi ast üst ilişkisine sahip olan organizasyonel yapılarla karşılamışızdır. Örneğin işletmelerin Genel Müdür’ den başlayarak en alt kademedeki personele kadar inen organizasyonel yapılar gibi. Burada söz konusu olan ilişkiler çoğunlukla pozisyonel bazdadır. Yani çok sayıda personel birbirlerine pozisyonel bazda bağımlıdır ve bu bağımlılık en üst pozisyondan en alt pozisyona kadar herkesi kapsar. Bu tarz bir sistemin uygulama ortamında canlandırılabilmesi için pozisyonel ilişkileri ifade edebilecek tablo yapılarına başvurulur. Özellikle pozisyonlar arasındaki ilişkiyi kendi içerisinde referans edebilen tablolar bu tarz ihtiyaçların karşılanması için biçilmiş kaftandır. Örneğin aşağıdaki tabloyu göz önüne alalım.</p>
<p><img src="/makale/images/mk117_1.gif" alt="" width="396" height="211" border="0" /></p>
<p>Tabloda, X şirketinin çalışan personelleri arasındaki pozisyonel hiyerarşiyi temsil eden bir yapı kullanılmıştır. Dikkat edilecek olursa, her satırın Amiri alanının değeri yine tablo içinde yer alan bir PersonelNo alanını işaret etmektedir. Bu ilişki Self Referencing Relations olarak adlandırılır. Elbette bu tarz bir sistemde hiyerarşinin nereden başladığının bir şekilde bilinmesi gerekir. Çünkü tepeden aşağıya doğru inmek için en üst birimin diğerlerinden tamamen benzersiz bir şekilde ifade edilmesine ihtiyaç vardır. Buradaki gibi Amiri alanının değeri Null olan bir satır kimseye bağlı değildir. Ancak kendisine bağlı olan bir organizasyonel hiyerarşi söz konusudur. İşte bunu sağlayabilmek için en tepede yer alacak satırın Amiri field’ ının değeri Null olarak belirlenmiştir. Tabloyu daha yakından analiz edecek olursak aşağıdaki hiyerarşik yapının oluşturulabileceğini kolayca görürüz.</p>
<p><img src="/makale/images/mk117_2.gif" alt="" width="288" height="344" border="0" /></p>
<p>İşte bu makalemizde yukarıda görmüş olduğumuz hiyerarşik yapıyı bir TreeView kontrolünde nasıl ifade edebileceğimizi incelemeye çalışacağız. Burada anahtar noktalar, <strong>Self Referencing Relations</strong> oluşturmak ve bu ilişkileri <strong>Recursive (Yinelemeli) </strong>bir Metod ile uygulayabilmektir. Öncelikle Self Referencing Relation tablomuz üzerinde görmeye çalışalım. Örneğin 5 numaralı PersonelNo değerine sahip satırımızı ele alalım. Bu satırın Amiri field’ ının değeri 2 dir. Yani organizasyonel yapıda, 5 numaralı satır aslında 2 numaralı satırın altında yer almaktadır. Kaldı ki, 2 numaralı satırda 1 numaralı satırın altındadır. Bu şekilde tüm satırların birbirleri ile olan bağlantılarını tespit edebiliriz.</p>
<p><img src="/makale/images/mk117_3.gif" alt="" width="368" height="198" border="0" /></p>
<table id="table4" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td>Self Referencing Relation özelliğini sağlayan tablolarda bir satır(satırların) bağlı olduğu satırın yine bu tabloda var olmasını sağlamak veri tutarlılığı (consistency) açısından önemlidir.</td>
</tr>
</tbody>
</table>
<p><br />Buradaki ilişkiyi uygulamalarımızda tanımlamak için DataRelation nesnelerini kullanabiliriz. Örneğin;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">DataRelation dr=new DataRelation("self",ds.Tables[0].Columns["PersonelNo"],ds.Tables[0].Columns["Amiri"],false);</pre>
<p>Bizim için ikinci önemli nokta bu ilişkiyi kullanacak olan Recursive(Yinelemeli) bir metodun geliştirilmesidir. Yinelemeli metodumuzu geliştirirken tablo içerisindeki her bir satırı ele alacak ve bu satırların var ise alt satırlarına da bakacak bir algoritmayı göz önüne almamız gerekiyor. Örneğin aşağıdaki Console uygulamasını ele alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">class Worker
{
SqlConnection con=new SqlConnection("data source=BURKI;database=Veritabanim;integrated security=SSPI");
SqlDataAdapter da;
DataSet ds;
DataRelation dr;
public void Baglan()
{
if(con.State==ConnectionState.Closed)
con.Open();
}
public void VeriCek()
{
da=new SqlDataAdapter("SELECT * FROM Kadro",con);
ds=new DataSet();
da.Fill(ds);
dr=new DataRelation("self",ds.Tables[0].Columns["PersonelNo"],ds.Tables[0].Columns["Amiri"],false);
ds.Relations.Add(dr);
}
//Recursive metodumuz.
public void DetayiniAl(DataRow dr,string giris)
{
Console.WriteLine(giris+dr["Personel"].ToString());
foreach(DataRow drChild in dr.GetChildRows("self"))
{
DetayiniAl(drChild,giris+"...");
}
}
public void AgacOlustur()
{
foreach(DataRow dr in ds.Tables[0].Rows)
{
if(dr.IsNull("Amiri"))
{
DetayiniAl(dr,"");
}
}
}
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
Worker wrk=new Worker();
wrk.Baglan();
wrk.VeriCek();
wrk.AgacOlustur();
}
}</pre>
<p>Bu uygulamada basit olarak tablomuzdaki verileri çekiyor ve organizasyonel yapıyı hiyerarşik olarak elde ediyoruz. Ağacımızı oluşturduğumuz AgacOlustur metodu, DataTable içerisindeki tüm satırları gezen bir foreach döngüsünü kullanıyor. Herşeyden önce bizim en tepedeki satırı bir başka deyişle en üstteki pozisyonu bulmamız gerekiyor.</p>
<p>Tablo yapımızdan Amiri alanının değeri Null olan satırın hiyerarşinin tepesinde olması gerektiğini biliyoruz. Bu nedenle if koşulu ile bu alanı buluyoruz. Ardından bulduğumuz satırı DetayiniAl isimli Recursive(Yinelemeli) metodumuza gönderiyoruz. Yinelemeli metodumuz gelen DataRow nesnesinin Child satırlarını gezen başka bir foreach döngüsü kullanıyor. Eğer Child satırlar var ise yinelemeli metodumuz tekrardan çağırılıyor. Bu işlem tüm satırların okunması bitene kadar devam edecektir. Sonuç itibariyle Console uygulamamızı çalıştırdığımızda aşağıdaki ekran görüntüsünü elde ederiz.</p>
<p><img src="/makale/images/mk117_4.gif" alt="" width="252" height="215" border="0" /></p>
<p>Gelelim, Windows uygulamamızda bu hiyerarşiyi nasıl şekillendirebileceğimize. İzleyeceğimiz yol Console uygulamamızdaki ile tamamen aynıdır. Tek fark bu kez bir DataRow’ a bağlı alt satırları alırken yinelemeli metodumuza parent node’ un <em>(ki burada bir TreeNode nesnesidir)</em> geçirilişidir. Örnek uygulamayı aşağıda bulabilirsiniz. <em>(Uygulamanın çalışmasını daha iyi anlayabilmek için Trace etmenizi öneririm.) </em>Burada özellike, child satırların hangi TreeNode’ nesnesine eklenmesi gerektiğinin tespiti son derece önemlidir. Dikkat ederseniz AgacOlustur metodumuzda ilk olarak Amiri alanının değeri Null olan satırı temsil edecek bir TreeNode nesnesi oluşturulmuş ve TreeView kontrolüne eklenmiştir. Daha sonra yinelemeli metodumuza bu TreeNode ve o anki DataRow nesneleri gönderilmiştir. Böylece DetayiniAl metodu içerisinde child satırların hangi TreeNode içerisine alınacağı tespit edilebilir.</p>
<p>TreeView kullanımı;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlConnection con=new SqlConnection("data source=BURKI;database=Veritabanim;integrated security=SSPI");
SqlDataAdapter da;
DataSet ds;
DataRelation dr;
private void Baglan()
{
if(con.State==ConnectionState.Closed)
con.Open();
}
private void VeriCek()
{
da=new SqlDataAdapter("SELECT * FROM Kadro",con);
ds=new DataSet();
da.Fill(ds);
dr=new DataRelation("self",ds.Tables[0].Columns["PersonelNo"],ds.Tables[0].Columns["Amiri"],false);
ds.Relations.Add(dr);
}
private void DetayiniAl(DataRow dr,TreeNode t)
{
foreach(DataRow drChild in dr.GetChildRows("self"))
{
TreeNode tnChild=new TreeNode(drChild["Personel"].ToString());
t.Nodes.Add(tnChild);
DetayiniAl(drChild,tnChild);
}
}
private void AgacOlustur()
{
foreach(DataRow dr in ds.Tables[0].Rows)
{
if(dr.IsNull("Amiri"))
{
TreeNode tn=new TreeNode(dr["Personel"].ToString());
treeView1.Nodes.Add(tn);
DetayiniAl(dr,tn);
}
}
}
private void Form1_Load(object sender, System.EventArgs e)
{
Baglan();
VeriCek();
AgacOlustur();
}</pre>
<p>Uygulamamızı çalıştırdığımızda aşağıdaki ekran görüntüsünü elde ederiz.</p>
<p><img src="/makale/images/mk117_2.gif" alt="" width="288" height="344" border="0" /></p>
<p>Bu makalemizde kendi satırlarını işaret eden satırların var olduğu ilişkileri taşıyan tablolarda, satır arasındaki hiyerarşik yapının Recursive(Yinelemeli) metodlar ile uygulama ortamına nasıl aktarılabileceğini incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/makale/images/SelfRefOrn.zip">Örnek kodlar için tıklayın.</a></p>2005-03-14T12:00:00+00:00ado.netrelationsrecursive functionsself referencingbsenyurtÇoğumuz çalıştığımız projelerde müdür,müdür yardımcısı gibi ast üst ilişkisine sahip olan organizasyonel yapılarla karşılamışızdır. Örneğin işletmelerin Genel Müdür’ den başlayarak en alt kademedeki personele kadar inen organizasyonel yapılar gibi. Burada söz konusu olan ilişkiler çoğunlukla pozisyonel bazdadır. Yani çok sayıda personel birbirlerine pozisyonel bazda bağımlıdır ve bu bağımlılık en üst pozisyondan en alt pozisyona kadar herkesi kapsar. Bu tarz bir sistemin uygulama ortamında canlandırılabilmesi için pozisyonel ilişkileri ifade edebilecek tablo yapılarına başvurulur. Özellikle pozisyonlar arasındaki ilişkiyi kendi içerisinde referans edebilen tablolar bu tarz ihtiyaçların karşılanması için biçilmiş kaftandır. Örneğin aşağıdaki tabloyu göz önüne alalım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=4f07a1d5-9540-4377-bebd-b804c1e294641https://buraksenyurt.com/trackback.axd?id=4f07a1d5-9540-4377-bebd-b804c1e29464https://buraksenyurt.com/post/Self-Referencing-Relations-ve-Recursive(Yinelemeli)-Metodlar-bsenyurt-com-dan#commenthttps://buraksenyurt.com/syndication.axd?post=4f07a1d5-9540-4377-bebd-b804c1e29464https://buraksenyurt.com/post/Baglantc4b1sc4b1z-Katmanda-Concurrency-Violation-Durumu-bsenyurt-com-danBağlantısız Katmanda Concurrency Violation Durumu2005-03-06T10:00:00+00:00bsenyurt<p>Değerli Okurlarım, Merhabalar.</p>
<p>Bağlantısız katman nesneleri ile çalışırken karşılaşabileceğimiz problemlerden bir tanesi güncelleme işlemleri sırasında oluşabilecek DBConcurrencyException istisnasıdır. Bu makalemizde, bu hatanın fırlatılış nedenini inceleyecek ve alabileceğimiz tedbirleri ele almaya çalışacağız. Öncelikle istisnanın ne olduğunu anlamak ile işe başlayalım. Bir DataAdapter nesnesine ait Update metodu güncelleme işlemleri için Optimistic(iyimser) yaklaşımı kullanan sql sorgularını çalıştırıyorsa DBConcurrencyException istisnasının ortama fırlatılması, başka bir deyişle Concurrency Violation (eş zamanlı uyumsuzluk) durumunun oluşması son derece doğaldır.</p>
<table id="table3" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td><strong>Optimistic yaklaşım modeli, Pessimistic</strong> <strong> yaklaşım modelinin aksine güncellenecek satırları kilitlemez. Buda sunucunun kilit açma, takip ve kapatma gibi işlemleri yapmaması dolayısıyla performansının artması anlamına gelir. Özellikle bağlantısız katman mimarisinde kullanılan optimistic yaklaşım modelinde tek sorun, güncelleme işlemlerini gerçekleştiren kullanıcıların bu işleri birbirlerinden habersiz şekilde yapmaları sonucu ortaya çıkabilecek durumlardır.</strong></td>
</tr>
</tbody>
</table>
<p><br />Örneğin belli bir satıra ait verileri güncellemek için kullanabileceğimiz aşağıdaki Sql sorgusunu ele alalım.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">UPDATE MAILS SET AD=@AD,SOYAD=@SOYAD,EMAIL=@EMAIL WHERE ID=@ORGID AND AD=@ORGAD AND SOYAD=@ORGSOYAD AND EMAIL=@ORGEMAIL</pre>
<p>Sorgumuz basitçe, MAILS isimli veritabanındaki AD, SOYAD ve EMAIL alanlarının değerlerini güncellemektedir. Bunu yaparkende optimistic (iyimser) yaklaşımını kullanır. Bu nedenle, Where koşulunda tabloya ait primary key alanı (ID) dahil olmak üzere tüm alanlar kullanılmaktadır. Böylece tüm alanların eşleştirme için kullanıldığı bir sorgu ortaya çıkar.</p>
<table id="table9" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td valign="top" width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td>Optimistic yaklaşımda ele alınan yukarıdaki sorgu modeli için Sql Server ve benzeri veritabanı sistemlerinde daha etkili yöntemlerde vardır. Örneğin Sql üzerinde <strong>timestamp</strong> tipinden (yada <strong>uniqueIdentifier</strong> tipinden) alanlar kullanılabilir. Timestamp türünden olan alanlar satır üzerinde yapılacak herhangibir güncelleme işleme sonrasında sistem tarafından otomatik olarak benzersiz bir karakter dizisi ile değiştirilen alanlardır. Böylece yukarıdaki sorgunun yaptığı işin aynısını aşağıdaki gibide yapabiliriz. (Buradaki Kontrol alanı tipi timestamp tipindendir.
<p>Update Mails Set Ad=@Ad,Soyad=@Soyad,Email=@Email <strong>Where Id=@OrgId</strong> And <strong> Kontrol=@Kontrol</strong></p>
<p>Bu ifadenin bize sağladığı en büyük avantaj elbetteki n sayıda alan içeren bir tabloda where ifadesinden sonra sadece iki alan kontrolü ile (primary key ve timestamp alanı) Concurrency Violation durumunu irdeleyebilecek olmamızdır. Biz makalemizde daha uzun olan yolu incelemeye çalışacağız. Lakin gerçek hayat modellerinde <strong>timestamp</strong> veya <strong>uniqueidentifier</strong> ve benzeri tipten alanların karşılaştırma işlemi için ele alınması daha doğru ve güçlü bir yaklaşım olacaktır.</p>
</td>
</tr>
</tbody>
</table>
<p><br />Böyle bir sorgunun neden olacağı istisnai durumu anlayabilmek için aşağıdaki senaryoyu göz önüne almakta fayda olacağı inancındayım. Senaryomuzda en az iki kullanıcı rol almaktadır. Bu kullanıcılarımıza A ve B takma isimlerini verdiğimizi düşünelim. Her iki kullanıcıda database' den MAILS tablosundaki verileri bağlantısız katmana DataAdapter sınıfına ait nesne örneği vasıtasıyla almaktadır.</p>
<p><img src="/makale/images/mk116_2.gif" alt="" width="582" height="468" border="0" /></p>
<p>A ve B verileri çektikten sonra, A kullanıcısı herhangibir satır üzerinde güncelleme işlemini uygular. Bu durumda DataAdapter nesnesinin UpdateCommand özelliğine karşılık gelen SqlCommand nesnesi, yukarıda yazdığımız sorguyu çalıştıracaktır. Bu sorguda, satırların orjinal değerleri ile veritabanındaki halleri aynı olacağından güncelleme işlemi başarılı bir şekilde gerçekleştirilecektir. Lakin B kullanıcısı şu anda, A' nın güncellemiş olduğu veri kümesinin eski haline bakmaktadır. Eğer B kullanıcısı, A kullanıcısının biraz önce güncellemiş olduğu satırı tekrar güncellemek isterse ne olacaktır?</p>
<p>İşte bu durumda, sorgu içindeki where koşuluna giren alan değerlerinin bağlantısız katmandaki orjinal halleri <em>(yani <strong>DataRowVersion</strong> numaralandırıcısı tipinden <strong>Original</strong> olan değerleri),</em> veritabanındaki tabloda az önce güncelleştirilmiş olan alanlara ait yeni değerler ile eşleşmeyeceğinden ilgili satır bulunamayacaktır. Bu da B kullanıcısının satırı update edememesine neden olur. Bu noktada CLR, DbConcurrencyException türünden bir istisnayı process içine fırlatacaktır. Dilerseniz bu hatayı basit bir uygulama yardımıyla elde etmeye çalışalım. Uygulamamız şimdilik sadece Update işlevini ele alacaktır. İlk olarak basit bir windows uygulaması açarak aşağıdakine benzer bir form ekranı oluşturalım.</p>
<p><img src="/makale/images/mk116_3.gif" alt="" width="420" height="256" border="0" /></p>
<p>Uygulamamız aşağıdaki field yapısına sahip olan ve Sql sunucusu üzerinde barındırdığımız MAILS tablosunu kullanacaktır. Tablomuzdaki ID alanı otomatik artan bir primary key olarak tanımlanmıştır.</p>
<p><img src="/makale/images/mk116_4.gif" alt="" width="376" height="114" border="0" /></p>
<p>Şimdide uygulama kodlarımızı yazalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">SqlConnection con;
SqlDataAdapter da;
DataSet ds;
/* SqlConnection nesnemizi oluşturduğumuz metodumuz. Bu metotda bağlantı bilgisini App.Config dosyasında tuttuğumuz connectionString isimli key' e ait value özelliğinden alıyoruz.*/
private void BaglantiHazirla()
{
try
{
con=new SqlConnection(ConfigurationSettings.AppSettings["connectionString"].ToString());
}
catch(SqlException hata)
{
MessageBox.Show(hata.Message);
}
}
/* Verileri yükleyen metodumuz parametre olarak aldığı string bilgiyi kullanan bir SqlDataAdapter nesnesi oluşturuyor. Daha sonra bu nesne yardımıyla DataSet' imiz dolduruluyor. Son olarak DataSet içindeki tablomuza ait primary key kolonu belirleniyor.*/
private void VerileriYukle(string sorguCumlesi)
{
BaglantiHazirla();
da=new SqlDataAdapter(sorguCumlesi,con);
ds=new DataSet();
da.Fill(ds);
ds.Tables[0].PrimaryKey=new DataColumn[]{ds.Tables[0].Columns["ID"]};
}
/* Veri Çek başlıklı butona tıklandığında, MAILS tablosundaki tüm verileri çekeceğimiz sorguyu çalıştıracak VerileriYukle metodunu çağırıyor ve sonuç kümesini DataGrid kontrolümüze bağlıyoruz. Ardından eğer SqlConnection nesnemiz açık ise kapatıyoruz.*/
private void btnVeriCek_Click(object sender, System.EventArgs e)
{
VerileriYukle("SELECT * FROM MAILS");
dgVeriler.DataSource=ds.Tables[0];
if(con.State==ConnectionState.Open)
{
con.Close();
}
}
/* VeriGüncelle metodu Update sorgusunu bizim tanımladığımız SqlDataAdapter nesnesini kullanarak güncelleme işlemini gerçekleştiriyor.*/
private void VeriGuncelle()
{
try
{
string guncellemeCumlesi="UPDATE MAILS SET AD=@AD,SOYAD=@SOYAD,EMAIL=@EMAIL WHERE ID=@ORGID AND AD=@ORGAD AND SOYAD=@ORGSOYAD AND EMAIL=@ORGEMAIL";
// Timestamp alanı olduğunda : Update Mails Set Ad=@Ad,Soyad=@Soyad,Email=@Email Where Id=@ORGID AND KONTROL=@KONTROL
SqlCommand cmdUpdate=new SqlCommand(guncellemeCumlesi,con);
/* Sorgumuz için gerekli parametreleri ekliyoruz. Parametre adlarını, veri tiplerini, boyutlarını ve DataTable daki hangi alanı source olarak alacaklarını belirliyoruz.*/
cmdUpdate.Parameters.Add("@AD",SqlDbType.NVarChar,50,"AD");
cmdUpdate.Parameters.Add("@SOYAD",SqlDbType.NVarChar,50,"SOYAD");
cmdUpdate.Parameters.Add("@EMAIL",SqlDbType.NVarChar,50,"EMAIL");
/* WHERE koşulunda kullanılan parametreleri giriyoruz. Burada parametre değerlerimiz field' ların orjinal değerleri olacak. Bunu sağlamak için SourceVersion özelliğine DataRowVersion numaralandırıcısının Original değerini atıyoruz.*/
cmdUpdate.Parameters.Add("@ORGID",SqlDbType.NVarChar,50,"ID");
cmdUpdate.Parameters["@ORGID"].SourceVersion=DataRowVersion.Original;
cmdUpdate.Parameters.Add("@ORGAD",SqlDbType.NVarChar,50,"AD");
cmdUpdate.Parameters["@ORGAD"].SourceVersion=DataRowVersion.Original;
cmdUpdate.Parameters.Add("@ORGSOYAD",SqlDbType.NVarChar,50,"SOYAD");
cmdUpdate.Parameters["@ORGSOYAD"].SourceVersion=DataRowVersion.Original;
cmdUpdate.Parameters.Add("@ORGEMAIL",SqlDbType.NVarChar,50,"EMAIL");
cmdUpdate.Parameters["@ORGEMAIL"].SourceVersion=DataRowVersion.Original;
// Where cümleciğinden timestamp veya uniqueIdentifier kullanıldığında yukarudaki parametre tanımlamaları yerine sadece Kontrol alanı için tek bir parametre tanımlamasının yapılması yeterli olacaktır.
// cmdUpdate.Parameters.Add("@KONTROL",SqlDbType.Timestamp,8,"KONTROL");
// cmdUpdate.Parameters["@KONTROL"].SourceVersion=DataRowVersion.Original;
da.UpdateCommand=cmdUpdate;
/* Son olarak Update metodunu çalıştırıyoruz.*/
da.Update(ds);
}
catch(SqlException hata)
{
MessageBox.Show(hata.Message);
}
}
private void btnGuncelle_Click(object sender, System.EventArgs e)
{
VeriGuncelle();
}</pre>
<p>Uygulamamızdan iki tane çalıştırdığımızı ve örneğin AD alanı A ve SOYAD alanı B olan satırların değerlerini sırasıyla ALİ ile VELİ olarak değiştirdiğimizi düşünelim. Eğer Güncelle butonuna tıklarsak işlemin başarılı bir şekilde gerçekleştirildiğini görürüz.</p>
<p><img src="/makale/images/mk116_1.gif" alt="" width="431" height="530" border="0" /></p>
<p>Şimdi ikinci kullanıcımız aynı satırın verilerini değiştirsin ve yine Güncelle butonuna bassın. Bu durumda aşağıdaki gibi bir istisna mesajını alırız.</p>
<p><img src="/makale/images/mk116_5.gif" alt="" width="485" height="561" border="0" /></p>
<p>Görüldüğü gibi ikinci kullanıcı update işlemini gerçekleştirmeye çalıştığında Concurrency Violation (eş zamanlı uyumsuzluk) durumu oluşacaktır. Bu belirleyici olarak DBConcurrencyException türünden bir istisnadır. Peki oluşan bu istisnai durumun üstesinden nasıl gelebiliriz? İlk akla gelen yöntem, istisna yakalandığında kullanıcıların verilerin en güncel hallerini elde etmeleri konusunda uyarılmalarını sağlamak olacaktır. Ancak bağlantısız katman üzerinde çalışırken, ikinci kullanıcılar bu örnekte olduğu gibi tek bir satırı güncellemek dışında yeni satır girişleri, satır silmeler ve hatta başka satır güncellemeleri gibi birden fazla sayıda işlemi gerçekleştirmiş olabilirler.</p>
<p>Eğer güncelleme yapılan kod satırlarını istisna yakalama mekanizmaları ile izlemez ve DbConcurrencyException hatasını yakalamazsak, kullanıcının o ana kadar yaptığı tüm değişiklikler uygulamanın istem dışı sonlanması nedeni ile kaybolacaktır. Bu elbetteki istenen bir durum değildir. Alternatif bir yol olarak, DataAdapter nesnesinin <strong> ContinueUpdateOnError</strong> özelliğine <strong>true</strong> değeri verilebilir. Bu durumda Update işlemi sırasında oluşacak olan hatalar göz ardı edilecektir. Yani Concurrency' ye neden olan satırlar var ise, bunların oluşturdukları istisnalar ortama fırlatılmayacaktır. Örneğimize bu durumu simüle edebileceğimiz bir checkBox kontrolü koyalım. Kullanıcı bu kutucuğu işaretler ise update işlemi sırasında oluşacak olan Concurrency Violation (eş zamanlı uyumsuzluk) istisnası görmezden gelinecektir. İlgili metodumuza ait kodlarımızı aşağıdaki gibi değiştirelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void btnGuncelle_Click(object sender, System.EventArgs e)
{
if(chkContinueUpdateOnError.Checked==true)
{
da.ContinueUpdateOnError=true;
VeriGuncelle();
}
else if(chkContinueUpdateOnError.Checked==false)
{
da.ContinueUpdateOnError=false;
VeriGuncelle();
}
}</pre>
<p>Şimdi, yine Concurrency olayına neden olacak şekilde değişiklikler yapalım. Yani her iki kullanıcımızda verileri çektikten sonra, birinci kullanıcımız belli bir satırı güncellesin. Ardından ikinci kullanıcımız aynı satırı tekrar güncellemeye çalışsın. Bu durumda her hangibir istisna fırlatılmaz ve uygulama istem dışı bir şekilde sonlanmaz. Dahası, ikinci kullanıcının yaptığı başka değişiklikler eğer var ise veritabanına başarılı bir şekilde yansıtılır.</p>
<p>Ancak halen daha sorunlu olan satıra ait kullanıcı yeterli bilgiye sahip değildir. <em>(Her ne kadar DataGrid bunu ünlem işaretleriyle belirtsede başka kontroller için bu özelliği sağlayamayabiliriz.) </em>Örneğin kullanıcıyı hangi satırların Concurrency Violation (eş zamanlı uyumsuzluk) istisnasına neden olduğu konusunda daha detaylı bir şekilde uyarabiliriz. Burada DBConcurrencyException sınıfının prototipi aşağıdaki gibi olan Row özelliği işimize yarayabilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public DataRow Row {get; set;}</pre>
<p>Bu özellik geriye hataya neden olan satırı işaret edebilecek bir DataRow nesne örneği döndürür. Böylece ilgili satıra ait detaylı bilgilere ulaşabiliriz. Ancak, istisnai durum Concurrency' e neden olan ilk satır görüldüğünde devreye girmektedir. Dolayısıyla ikinci kullanıcının elinde Concurrency istisnasına neden olacak birden fazla satır varsa tüm bu satırları yakalamak için alternatif bir yol uygulamamız gerekmektedir. Ado.Net mimarisinde yer alan DataSet, DataTable ve DataRow sınıflarının <strong>HasErrors</strong> özellikleri bu noktada bizim işimize yarayabilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public bool HasErrors {get;}</pre>
<p>Bu özellik bool tipinden olup, herhangibir hata var ise geriye true değerini döndürecektir. Concurrency durumunu bu hatalar arasında sayabiliriz. Şimdi uygulama kodlarımıza aşağıdaki metodu ekleyelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void SonHaliAl()
{
string satirBilgi;
if(ds.Tables[0].HasErrors)
{
foreach(DataRow dr in ds.Tables[0].Rows)
{
if(dr.HasErrors)
{
satirBilgi=dr["AD"].ToString()+" "+dr["SOYAD"].ToString()+" Başkası tarafından değiştirilmiş. Satırın son halini elde etmek ister misiniz?";
if(MessageBox.Show(satirBilgi,"Son hali al",MessageBoxButtons.YesNo,MessageBoxIcon.Question)==DialogResult.Yes)
{
SqlCommand cmdSonHaliAl=new SqlCommand("SELECT * FROM MAILS WHERE ID="+(int)dr["ID"],con);
if(con.State==ConnectionState.Closed)
{
con.Open();
}
SqlDataReader drGuncelSatir=cmdSonHaliAl.ExecuteReader(CommandBehavior.SingleRow);
drGuncelSatir.Read();
dr.BeginEdit();
dr["ID"]=drGuncelSatir["ID"];
dr["AD"]=drGuncelSatir["AD"];
dr["SOYAD"]=drGuncelSatir["SOYAD"];
dr["EMAIL"]=drGuncelSatir["EMAIL"];
// Timestamp veya uniqueIdentifier tipinden bir alan kullandıysak (örneğimizdeki KONTROL alanı gibi) onuda güncellememiz gerekir.
// dr["KONTROL"]=drGuncelSatir["KONTROL"];
dr.EndEdit();
con.Close();
}
}
}
ds.Tables[0].AcceptChanges();
}
}</pre>
<p>Bu metod ile ilk olarak dataTable' ın HasErrors özelliğine bakıyoruz. Eğer bir hata var ise, her bir satırı taramaya başlıyoruz. Her bir satırın HasErrors özelliğinin değerine bakarak hatalı satırları, bir başka deyişle Concurrency Violation (eş zamanlı uyumsuzluk)' a neden olanları buluyoruz. Sonra, hatalı satırın primary key olduğunu bildiğimiz ID değerini kullanarak ilgili satırın birinci kullanıcı tarafından güncellenmiş olan halini çekiyoruz. Bunu yaparkende SqlCommand ve SqlDataReader nesnelerimizi kullanıyoruz. Burada ID alanı primary key olduğundan ve benzersiz olarak satırları işaret edebildiğinden tek satır döneceğinden eminiz. Bu nedenle <strong> CommandBehavior</strong> numaralandırıcısının <strong>SingleRow</strong> değerini kullandık.</p>
<p>Bu bize performans açısından ekstra zaman kazandıracaktır. Ardından Concurrency Violation (eş zamanlı uyumsuzluk) içinde kalan satırın alanlarına ait değerleri, asıl veritabanından çektiklerimiz ile değiştiriyoruz. İşte bu noktadan sonra eğer kullanıcı tekrarda aynı satırları update eder ise hiç bir problem ile karşılaşmayacaktır. Nitekim, satırların <strong>DataRowVersion.Original</strong> değerleri veritabanındaki en güncel halleri ile değiştirilmiş olacaktır. Dilersek, ikinci kullanıcının o ana kadar yapmış olduğu ve Concurrency Violation (eş zamanlı uyumsuzluk) altında kalan değişikliklerin tekrardan yazılmasını sağlayabiliriz.</p>
<p>Tek yapmamız gereken Concurrency Violation (eş zamanlı uyumsuzluk)' de kalan alanların o anki değerlerini bir şekilde saklamak, alanların güncel hallerini çekerek orjinal değerleri yeni hallerine set etmek ve son olarak sakladığımız alan değerlerini tekrardan veritabanına göndermektir. Yazdığımız SonHaliAl isimli metodu catch bloğu içerisinde çağırmaktayız. Nitekim Concurrency Violation (eş zamanlı uyumsuzluk) durumları ancak SqlDataAdapter nesnemizin Update metodunu çağırdıktan sonra ortaya çıkan istisna içerisinde ele alınabilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void VeriGuncelle()
{
try
{
// diğer kod satırları
da.Update(ds);
}
catch(DBConcurrencyException)
{
SonHaliAl();
}
}</pre>
<p>Böylece geldik bir makalemizin daha sonuna. Bu makalemizde kısaca bağlantısız katmanda meydana gelebilecek eş zamanlı çakışmaları nasıl ele alabileceğimizi incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hoşçakalın.</p>2005-03-06T10:00:00+00:00ado.netconcurrencydisconnected layerbsenyurtBağlantısız katman nesneleri ile çalışırken karşılaşabileceğimiz problemlerden bir tanesi güncelleme işlemleri sırasında oluşabilecek DBConcurrencyException istisnasıdır. Bu makalemizde, bu hatanın fırlatılış nedenini inceleyecek ve alabileceğimiz tedbirleri ele almaya çalışacağız. Öncelikle istisnanın ne olduğunu anlamak ile işe başlayalım. Bir DataAdapter nesnesine ait Update metodu güncelleme işlemleri için Optimistic(iyimser) yaklaşımı kullanan sql sorgularını çalıştırıyorsa DBConcurrencyException istisnasının ortama fırlatılması, başka bir deyişle Concurrency Violation (eş zamanlı uyumsuzluk) durumunun oluşması son derece doğaldır.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=6ca2c94a-1623-4967-89ca-6b12a3cd50160https://buraksenyurt.com/trackback.axd?id=6ca2c94a-1623-4967-89ca-6b12a3cd5016https://buraksenyurt.com/post/Baglantc4b1sc4b1z-Katmanda-Concurrency-Violation-Durumu-bsenyurt-com-dan#commenthttps://buraksenyurt.com/syndication.axd?post=6ca2c94a-1623-4967-89ca-6b12a3cd5016