Language Integrated Query-2

Önceki yazımızda da bahsettiğim gibi elimden geldiği kadar linq konusunda farklı konulara değinmeye çalışacağım demiştim, tabi mutlaka bir linq fonksiyonlarının kullanımını içeren bir yazı yazacağım.

Normalde büyük çoğunluğumuzun tahmin ettiği ve bildiği gibi linq hız konusunda çok daha avantajlı ama bazı durumlar var ki bazı durumlar düşündüğümüz gibi değil.

Aşağıdaki kod bloğunda aynı işi yapan iki grup kod parçacığı olacak, bir tanesi aynı işi for döngüsüyle yapıyor olacak diğeri ise linq metodlarını kullanıyor olacak.


var listSize= 10000000;
var stopwatch= new Stopwatch();
stopwatch.Restart();
var s = Enumerable.Range(1,listSize)
       .Select (n=>n*2) 
       .Select (n=> Math.Sin((2*Math.PI*n)/1000 )) 
       .Select (n=> Math.Pow(n,2))
       .Sum();
stopwatch.Stop();
Console.WriteLine("LINQ {0} items in {1} ms",listSize,stopwatch.ElapsedMilliseconds);
-------------------------------------------------------------------------------------
stopwatch.Restart();
double sum=0;
for(int n=1; n<=listSize; n++)
{
  var a=n*2; 
  var b=Math.Sin((2*Math.PI*a)/1000); 
  var c=Math.Pow(b,2); 
  sum +=c;
}
stopwatch.Stop();
Console.WriteLine("for loop {0} items in {1}",listSize,stopwatch.ElapsedMilliseconds);

Bu kod parçacıklarını linqpad editörü ile derledim.Beklediğimiz üzere linq metodu ile yazılan blok daha hızlı çalışmadı.Klasik yöntem for döngüsü bir miktar da olsa daha performanslı çalıştı.

Böyle olmasının sebebi Enumerable tipli döngülerde lamda expressionın performans sorunu yaşaması, zaten yazılımcılar for,foreach döngülerinden bir bakıma kurtulmak iç içe for döngüleri yazmamak için linq kullanmaktadır. Linq de aslında içeride kendine has bir döngü yapısı olduğu için bir döngü içerisinde lamda expression kullanmak ekstradan zaman kaybettirmektedir.Tabiki döngü içerisinde lamda expression kullanmakta ısrarcı iseniz;

var listSize= 10000000;
var stopwatch= new Stopwatch();
var s = Enumerable.Range(1,listSize).AsParallel()
        .Select (n=>n*2) 
        .Select (n=> Math.Sin((2*Math.PI*n)/1000 ))
        .Select (n=> Math.Pow(n,2))
        .Sum();
stopwatch.Stop();
Console.WriteLine("LINQ {0} items in {1} ms",listSize,stopwatch.ElapsedMilliseconds);

Enumerable döngünüze .AsParallel() extension methodunu ekleyerek, linq kod bloğunuzun kayda değer şekilde hızlı çalışmasını sağlayabilirsiniz.Bunun sebebide arka tarafta daha fazla thread açarak işlemi yapmasıdır.

Func ve Action Tipleri

İki generic yapıda olan Action ve Func delegelerini kullnarak yukarıda yazmış olduğumuz kodu tekrar düzenlemeye çalışacağız. Öncelikle biraz Func ve Action tiplerinden bahsedelim. Aslında bu iki tipte bize kolay yoldan delege tanımlamamızı ve kullanmamızı sağlıyor.

Aşağıdaki örnekte int tipinde bir parametre alan funcString tipi geriye string değerinde bir result döndürmektedir.

    Func<int,string> funcString =(x) =>string.Format("string = {0}", x);

Action tipi int tipinde parametre alıp geriye herhangi bir değer dönmez,dönüş tipi void’dir.

 Action<int> action= (int x) => Console.WriteLine("Write {0}", x);

Şimdi yukarıda yazmış olduğumuz code parçacığını birde func ve action tipleri ile yazalım.

 var stopwatch = new Stopwatch();
 var listSize = 10000000;
 Func<int, double> funcCalculate = (x) =>
 {
 var s = Enumerable.Range(1, x).AsParallel()
 .Select(n => n * 2)
 .Select(n => Math.Sin((2 * Math.PI * n) / 1000))
 .Select(n => Math.Pow(n, 2))
 .Sum();
 return s;
 };
 Action<double> write = (double x) => Console.WriteLine("LINQ {0} items in {1} ms", x, stopwatch.ElapsedMilliseconds);
 write(funcCalculate(listSize));
 stopwatch.Stop();
 Console.ReadKey();

Yukarıda açık olarak yazmış olduğumuz hesaplama işlemini funcCalculate delegesinin içine yazdık. Delege int tipinde bir değer alıp double veri tipinde result dönmektedir. Write actionı ise funcCalculate delegesini de kapsayan bir actiondır. Action ekrana functiondan gelen değeri döndürmektedir. Action tipini sadece bu kapsamda ele almamak daha detaylı bir şekilde kullanmak mümkündür.

Keyifli Çalışmalar.

 

Linq (Language Integrated Query)

Language Integrated Query, kısaca Linq .Net framework 3.5 sürümü ile birlikte tanımlı gelen bir özelliktir. .Net  framework 2.0 sürümünde eklentilerle beraber kullanılıyordu. Linq sayesinde veritabanı ile kullanılan bir programlama dili arasında kodsal bir bütünlük sağlanmıştır. Sql ifadeslerine benzeyen söz dizimi ile farklı tiplerdeki verilerin sorgulanmasını sağlayan bir metadolojidir. Bu yazıda tüm linq metodlarını sıra sıra değinerek anlatmayacağım,  biraz daha farklı noktaları değinmek amacım aslında. Linq kodlarını derlemek için Linqpad editörünü kullanıyor olacağım.

Lamda Expressions

Lambda ifadeleri anonim fonksiyonları ayrıştırma için güzel ve kompakt bir sözdizimi, şimdi  customer nesnesinin içinden e-mail alanı null olmayan verilerini sorgulayacağız. Bu kadar basit bir sorguda bile lamda ifadesinin ne kadar sadeleştirdiğini görmüş olacağız.


//eski söz dizimi

customers.Where(delegate(Customer c)) 

{return c.Email!=null; })

//yeni söz dizimi

customers.where(c=>;c.Email!=null)

Extension Methods

Extension Methods’lar bir veri tipi üzerinde ekstra bir değişiklik yapmadan o tipi kolayca extend etmemizi sağlayan metodlardır. Bu metodları aslında birer static metod olarak düşünebiliriz, sadece tanımlamış olduğunuz veri tipleri üzerinden direk erişebilirler ve o veritipini tanımladığınız metoddaki içeriğe göre düzenleyip size geri dönen metodlardır. Extension methodslar da .Net Framework 3.5 ile kullanılmaya başlanmıştır. Şimdi basit bir extension method yazalım.


//eski yöntem

IslemTarihi = row["ISLEMTARIHI"] == DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(row["ISLEMTARIHI"])

//extension methods

 public static DateTime GetDatetimeValue(this DataRow row)
 {
 return row[0] == DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(row[0]);
 }

row["ISLEMTARIHI"].GetDatetimeValue();

Açıklama: Eski yöntem ile cast işlemini her item için istediğimiz tipe göre her satıra teker teker yazacaktık, bunun için ihtiyacımıza göre extension metodu bir kere yazdıktan sonra istediğimiz yerde kullanabiliriz. Burada metodun extension olmasını sağlayan this kelimesidir. Siz de ihtiyacınıza göre methoda başka parametreler verip kullanabilirsiniz.

Anonymous Types

Bir method içerisinde basit bir clasa ihtiyaç duyduk, gidip clasın propertylerini (private ve public), set metodunu tanımlayıp ondan nesne üretip kullanacaktık.Bunun yerine anonymous type bize tek satırla hem private hemde public propertylerinin oluşturulmasına hemde yönetilmesine imkan veriyor.

//bilindik yöntem
public class OzlukBilgiler()
{ 
  private string _adi;
  private string _babaAdi;
  private string _soyadi; 
  private string _telefonNo;

 public string Soyadi
 {
 get { return _soyadi; }
 set
 {
 if (_soyadi != value)
 {
 _soyadi = value;
 OnPropertyChanged("Soyadi");
 }
 }
 }
 public string BabaAdi
 {
 get { return _babaAdi; }
 set
 {
 if (_babaAdi != value)
 {
 _babaAdi = value;
 OnPropertyChanged("BabaAdi");
 }
 }
 }
 public string Adi
 {
 get { return _adi; }
 set
 {
 if (_adi != value)
 {
 _adi = value;
 OnPropertyChanged("Adi");
 }
 }
 }
 public string TelefonNo
 {
 get { return _telefonNo; }
 set
 {
 if (_telefonNo != value)
 {
 _telefonNo = value;
 OnPropertyChanged("TelefonNo");
 }
 }
 }
public OzlukBilgiler(string Adi, string Soyadi, string BabaAdi,string TelefonNo)&amp;amp;amp;nbsp; &amp;amp;amp;nbsp; 
{ 
this.Adi = Adi; 
this.Soyadi = Soyadi;
this.BabaAdi = BabaAdi; 
this.TelefonNo = TelefonNo;
}
}

public class OzlukLoglama() 
{ 
OzlukBilgiler obj = new OzlukBilgiler("Ahmet", "Akmaz", "Mehmet","02121234567"); 
string Loglanacak = string.Format("Hata Mesajı: {0}\nİsim: {1} {2}\nSoyad: {3}\n\nKullanıcıId: {4}\nBaba Adı: {5}\nTelefon: {6}", obj.Adi, obj.Soyadi(), obj.BabaAdi, obj.TelefonNo); 
File.WriteAllText(Loglanacak, @"C:\Log\Log.txt"); MessageBox.Show(Hata.Mesaj, Hata.Tarih); }
}

 

public class OzlukLoglama()
{//anonymous type
 var obj = new { Adi = "Ahmet", Soyadi = "Akmaz", BabaAdi="Mehmet", TelefonNo="2121234567" };
 string Loglanacak = string.Format("Hata Mesajı: {0}\nİsim: {1} {2}\nSoyad: {3}\n\nKullanıcıId: {4}\nBaba Adı: {5}\nTelefon: {6}", obj.Adi, obj.Soyadi(), obj.BabaAdi, obj.TelefonNo);
 File.WriteAllText(Loglanacak, @"C:\Log\Log.txt");
 MessageBox.Show(Hata.Mesaj, Hata.Tarih);
}


Yield Keyword
İterasyon dediğimizde aklımıza gelen ilk yapı foreach döngüleri; arka planda bir iterator yapısını kullanıyorlar.Bir iterator yaratmak için IEnumerable, IEnumarator, IEnumerable <T>,
IEnumerator <T> interfacelerinin birinin dönüş tipinde olması gereklidir.

public static IEnumerable  GetNumbers()
 {
 yield return "1";
 yield return "2";
 yield return "3";
 yield return "4";
 yield return "5";
 yield return "6";
 yield return "7";
 }

 static void Main(string[] args)
 {
 foreach (var item in GetNumbers())
 {
 Console.WriteLine(item);
 }
 Console.ReadKey();
 }

 

Açıklama: Yukarıda da gördüğünüz gibi GetNumbers() metodunun içinde birden fazla return ifadesi kullanarak rakamları ardı sıra yazdığını gördük, bildiğiniz üzere

return ifadesinin böyle bir kullanımı yoktu, burdaki dikkat edilecek nokta, compiler her yield keywordünü gördüğünde bundan önceki yield keywordünün kaldığı yeri saklamakta ve her çağırıldığında baştan başlamak yerine kaldığı yerden devam etmektedir.

Keyifli çalışmalar.

Transaction Mimarisi-2

Xact_State()  Fonksiyonu

 

Try-catch yapısını kullandığımızda bir hata aldığımızda transaction rollback edilir. Transaction’ın o anki durumu hakkında bilgi alıp durumuna göre işlem yapabiliriz. Transaction’ın durumu hakkında bilgi almak için xact_state() fonksiyonu kullanılır.

1–> Transaction’ın aktif olduğunu gösterir.

0–> Transaction’ın pasif olduğunu gösterir.

2–> İlgili transaction’ın sonlandırılabilir bir transaction olmadığını gösterir.


begin tran
insert into Production.Culture values ('tr','Turkish',GETDATE())
if xact_state()=1
begin
print 'aktif bir transaction vardır.'
end
rollback tran

Nested Transactions

Sql server bir transaction bitmeden başka bir transaction başlatmanıza izin verir. Nested transaction için kullanılan “commit” ifadesi en içteki transaction için kullanılır ve @@trancount değerini 1 azaltır. Commit işleminden farklı olarak rollback işlemi ise tüm transactionları sonlandırır  ve @@trancount değerine 0 atanır.

Transaction Yönetimi

Açık unutulmuş bir transaction içinde sistemdeki bir objeyi kilitleyecek bir işlem yapılıyorsa transaction sonlanıncaya kadar o objeye erişilmez. Bu obje çok önemli bir tablo ise bu durum kabul edilemez. Sistem üzerindeki açık olan transactionlar kontrol edilip duruma göre başka bir obje kitleniyorsa, o transaction sonlandırılır.

Dbcc Opentran Komutu

Dbcc opentran komutu ile veri tabanındaki açılmış olan ve hala sonlandırılmamış transacationları bulabiliriz.


begin tran

insert into Production.Culture values ('tr','Turkish',GETDATE())
dbcc opentran
if xact_state()=1
begin
print 'aktif bir transaction vardır.'
end

rollback tran

çıkacak olan result aşağıdadır.

dbccopentran

Dbcc Inputbuffer Komutu

Daha önceden açılmış bir transactionın içeriğine bakıp hangi komutların o anda çalıştığını görmek için dbcc inputbuffer komutunu çalıştırabiliriz. Yukarıdaki result sonucunda görünen spid değerini parametre olarak alan dbcc inputbuffer komutuna verdiğimizde transactionın o anki içeriğini görebiliriz.


begin tran

insert into Production.Culture values ('ger','Germany',GETDATE())
dbcc inputbuffer(52)
if xact_state()=1
begin
print 'aktif bir transaction vardır.'
end

rollback

inputbuffer

Keyifli çalışmalar.

Transaction Mimarisi

Transaction mimarisi, veri tabanlarının bence en önemli parçasını oluşturmaktadır. Yapılacak bir işin basamaklarında herhangi bir hata alındığında o hataya kadar yapılan işleri tamamen geri alabilir yada bizim belirlediğimiz düğüme geri dönebiliriz.

Temel olarak transactionlar aşağıdaki 4 özelliğe sahiptirler.

  • Atomicity: Yapılan bir işlem birden fazla alt işlem içerebilir ve bu alt işlemlerden herhangi birisi hatalı sonuçlanırsa, tüm işlemler geri alınır.
  • Consistency: Transaction sonrasında yani işlemler bittiğinde veri bütünlüğü sağlanmalıdır. Kategori tablosunda olmayan bir kategori için, ürünler tablosuna herhangi bir kayıt yapılmamalıdır.
  • Isolation: Birden fazla transactionın aynı anda yada farklı zamanlarda çalışması durumunda birbirlerini etkilememesi gerekmektedir.
  • Durability: Herhangi bir hata alındığında, sistemin tamamen ilk haline dönebilme yeteneğidir.

Autocommit Transaction Yapısı

Bütünleşik  olan işler için uygun olmayan Sql Server’in varsayılan transaction yapısıdır. İki tane dml işlemi düşünelim bir tanesi hata almış olsa bile diğeri için işlem gerçekleşecektir yani sql server her iki işlem için ayrı ayrı transaction açmaktadır.

Implicit Transaction Yapısı

Sql server varsayılan olarak autocommit transaction yapısını kullanmaktadır. Eğer aşağıdaki sorguyu çalıştırıp implicit yapsına geçtiğinizde artık sql server sizden ya commit (işlemin başarılı olduğu) yada rada rollback (işlemin başarısız olduğunu) belirtmenizi bekleyecektir. Bu cümlecikleri yazmadığınız takdirde crud işlemlerinden gerçek anlamda reaksiyon alamayacaksınızdır. Ayrıca eğer rollback yada commit komutlarını eklemezsek açılan transaction kapanmayacak ve sisteme yük getirecektir.

SET IMPLICIT_TRANSACTIONS ON komutuyla bu transaction yapısına geçebilir ve SET IMPLICIT_TRANSACTIONS OFF komutuyla bu yapıyı kapatabilirsiniz.

Hatırlatma: Kısa bir dönem oracle ile de çalışmış biri olarak oracle ms sql serverdan farklı olarak varsayılan transaction yapısı olarak implicit transaction yapısını kullanmaktadır.

Explicit Transaction Yapısı

Sql server’da en çok kullanılan ve tavsiye edilen transaction yapısıdır. Bu yapıda transactionın ne zaman başlayacağına ve ne zaman sonlanacağı bizim tarafımızdan belirlenir. Hemen basit bir transaction yazımına geçelim.

Öncelikle belirtmem gereken ve bizim işimize çok yarayacak olan iki tane komut var;

@@ERROR : En son çalıştırılan sql ifadesi sonucunda eğer bir hata oluşursa bu hata numarasını tutar.Başarılı bir şekilde çalışmışsa değer ‘0’ dönecektir.

@@TRANCOUNT: Sql server üzerinde akitf olarak çalışan yani kapatılmayan transaction sayısını tutmaktadır.




use AdventureWorks2014

select count (*)firstcount from HumanResources.Department

declare @error int

begin TRANSACTION

insert into HumanResources.Department(Name, GroupName) values ('Finanace','Banking')

set @error =@@error --insert işlemi sırasında bir hata olduysa değişkenimize o hata numarasını set ediyoruz.

if (@error <> 0) goto error_handler

select count (*)secondcount from HumanResources.Department



insert into HumanResources.Department(Name, GroupName) values ('Finanace','Banking')

set @error =@@error --insert işlemi sırasında bir hata olduysa değişkenimize o hata numarasını set ediyoruz.

if (@error <> 0) goto error_handler

COMMIT TRANSACTION

error_handler:

if (@error <> 0)

begin

ROLLBACK TRANSACTION

end

select count (*) thirthcount from HumanResources.Department

Yukarıdaki transaction bloğunda ilgili tablomuza basit 2 tane dml işlemlerini kontrollü bir şekilde yapmaya çalıştık. Herhangi bir adımda hata aldığımız an bütün işlemler en başa dönecek ve sistemimiz eski ve ilk haline dönmüş olacaktır. Kod bloğunda transaction başlamadan, ilk insert ve transaction sonrasında kayıtlarımızın sayısını çeken sorgular yazdık. İlk işlemden sonra +1 artan kayıt sayımız ikinci insert işleminde hata alacağından eski sayısına geri dönmüş olacaktır.

Yazıyı bitirmeden;

SAVE TRANSACTION: Bizim yazdığımız blokta bu komutu kullanmadık, bu yüzden sistem en başına dönmüştür siz herhangi bir hata aldığında en başa dönmek yerine bu komutu yazdığınız yere kadar olan kısım sistemimizde kayıtlı olacaktır.

Keyifli çalışmalar.

 

Interface Kavramı

Önceki yazıda abstract kavramından bahsetmiştik, Bu yazıda da benzerlik ve farklılıkları olan interface kavramına değinmeye çalışacağım.

Interface kelime anlamı olarak “arayüz” anlamına gelmektedir. Yani asıl clasa bir şekilde referans olacaktır. Interface kendi elamanlarını referans ettiği classa miras bırakacak ve class istediği şekilde bu itemleri kullanacaktır. Yalnız bir class bir interface tarafından extend edilmiş ise, interface’in zorunlu kıldığı yetkinliklerede sahip olmuş demektir. Interface tanımlaması yapılırken zorunlu olmasada dikkat etmemiz gereken nokta; Interface tanımlarının başına “I” harfini koyuyor olmamız gerekmektedir.

Interface tanımı;


namespace ApplicationInterface
{
 interface IOrnekInterface
 {
 //Metod
 void OrnekMetod();
 //property
 int OrnekProperty { get; set; }
 //Event
 event EventHandler OrnekEvent;
 //Indexer
 string this[int index] { get; set; }

}
}

Interface’imizi tanımladıktan sonra sıra geldi extend edeceği classımızı tanımlamaya. Classımızı tanımlarken; interface’imizi classa extend ediyoruz. Bu esnada derleyici size kızacak ve interface’in zorunlu kıldığı elemanlarını da implement etmenizi isteyecektir. Bu durum oop tasarımcısı için bir bütünlük sağlamasında yardımcı olacaktır. Ctrl + . kombinasyonuyla kolayca implement edebiliriz. Böylece gövde tanımları yapılmayan metod,index tanımlamaları sizin kullanımınıza verilmiş oldu, istediğimiz şekilde içlerini doldurabiliriz.

Class tanımı;


class OrnekClass:IOrnekInterface
 {
 public void OrnekMetod()
 {
 throw new NotImplementedException();
 }

public int OrnekProperty
 {
 get
 {
 throw new NotImplementedException();
 }
 set
 {
 throw new NotImplementedException();
 }
 }

public event EventHandler OrnekEvent;

public string this[int index]
 {
 get
 {
 throw new NotImplementedException();
 }
 set
 {
 throw new NotImplementedException();
 }
 }
 }

Artık main metodumuzun içinde şöyle bir tanım yapabiliriz;


 static void Main(string[] args)
 {
 IOrnekInterface obj= new OrnekClass();
 obj.OrnekMetod();
 }

 

Ornek Classımız IOrnekInterface tarafından extend edildiği için böyle bir tanımlama yapabildik. Tabiki oluşturduğumuz nesnenin tipini OrnekClass’ımız üzerinden de verebilir ve aynı şekilde OrnekMetodu kullanabilirdik. Böyle bakıldığında abstract class ile interface arasında çok farklılık görünmeyebilir, hatta birtakım benzerliklerden de kısaca bahsedelim.

  • Interface’in tüm elemanları extend edilmek zorundaydı, abstract classında tüm elemanları virtual tanımlayabiliriz.  
  • Interface elemanlarının gövdeleri interface içinde tanımlanamazdı. Abstract classında virtual elemanları aynı şekildedir.

Fakat C# ‘ta bir sınıf, tek bir sınıfı inherit edebilirken, birçok interface tarafından extend edilebilmektedir.Bir classımız ihtiyacı olduğu metod yada propertylere göre birden fazla interface tarafından extend edilebilir.Interface ve Abstract kavramları arasındaki en büyük fark budur. Classımıza birde IEnumareble interface’ini de extend olarak ekliyoruz.İhtiyacımıza göre birden çok interface classlarımıza extend edebiliriz.


class OrnekClass : IOrnekInterface,IEnumerable
{
public void OrnekMetod()
{
throw new NotImplementedException();
}

public int OrnekProperty
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}

public event EventHandler OrnekEvent;

public string this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}

public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}

Konuyu özetlersek;

  • Interfaceler, metod, property, event ve indeksleyiciler barındırabilirler.
  • Interfaceler elemanlarının gövdelerini barındırmazlar. Sadece tanımları vardır.
  • Bir sınıf bir interface tarafından extend edildiğinde o interface’in tüm elemanlarını implemente etmek zorundadır.
  • Sınıflarda olduğu gibi interfaceler için de tüm erişim belirleyicileri kullanabiliriz. Interface elemanları için default erişim belirleyici public’tir. Kendimiz belirlemek istesek de derleyici hata verecektir.

İyi Çalışmlar.

 

Abstract Kavramı

Öncelikle bu kavrama neden ihtiyaç duyarız, bize ne gibi avantajlar sağlıyor, bunları araştırmamız gerekir. Bunun için basit bir yapı kurarak işe başlayalım dedim. Bir tane ‘Personel’ isimli base classımız ve bundan türeyen ‘Uzman’ ve ‘Lider’ classlarımız olsun.Base classımızda bu iki sınıfın maaşlarını hesaplayan bir metodumuz olsun. Oluşturduğum yapının kodları aşağıdaki gibidir.


class Personel
 {
 public decimal Maas { get; set; }
 public decimal ZamOrani { get; set; }

public decimal MaasHesapla()
{
  return Maas + (Maas * ZamOrani);
 }
}

class Uzman : Personel { }

class Lider : Personel { }

Main metodu içinde Uzman ve Lider classlarından türeyen nesnelerimizin maaşlarını hesaplatalım.


static void Main(string[] args)
 {
  var uzman = new Uzman()
 {
   Maas = 100,
   ZamOrani = 0.25m
 };
 Console.WriteLine("Uzman Maaşı: {0}", uzman.MaasHesapla());

var lider= new Lider()
{
   Maas = 200,
   ZamOrani = 0.30m
 };
  Console.WriteLine("Lider maaşı: {0}", lider.MaasHesapla());
  Console.ReadKey();
}

Programı çalıştırdığımızda çıktı aşağıdaki gibi olacaktır.

capture1

Maaş hesaplamak için base classımızdaki ‘MaasHesapla()’ metodunu kullandık, Her birim için aynı tip maaş hesaplaması geçerli olsaydı bu bizim için gayet kullanışlı bir yapı olacaktı. Her birimin maaş hesaplama fonksiyonu olabilir ama fonksiyonların içerikleri farklı olabilir yani kendi içlerinde maaşlar farklı şekilde hesaplanabilir. Bu durumda ortak olan sadece MaasHesapla() metodu olup, gövde kısımları kendi child classlarımızda farklılık gösterecek. Bu durumu şu şekilde de aşabilirdik; base classımızdaki MaasHesapla() metodunu virtual yapıp, child classlarımızda (Uzman ve Lider) MaasHesapla() metodunu override edebiliriz bu şekilde yapmadan abstract yapısını kullanacağız.  Base classımız ve MaasHesapla() metodumuz abstract tipte tanımlayacağız. Böyle bir tanımlama yaparak artık base classlarımızdan türeyen her yeni child classımızda MaasHesapla() metodu zorunlu hale gelecek ve metodun gövde kısmını child classlarımızda yazacağız buda bize her child classımız için farklı bir yöntemle maaş hesaplama şansı tanayacak.Bir oop tasarımında, sınıf içerisinde abstract üyeler tanımlayarak, bu sınıftan türetilen sınıfları, tanımladığımız abstact üyelerin gövdelerini kodlamaya zorlayabiliriz. Yani örneğimizden yola çıkarsak eğer sen Personel clasından miras alan bir sınıf isen mutlaka bana MaasHesapla() metodunu sağlamak zorundasın demiş oluyoruz. Bu durumda yeni yapımız aşağıdaki gibi olacaktır. Maaş hesaplamasında farklılar oluşması için Uzman classımıza ‘prim’ alanı ekledim.

abstract class Personel
 {
 public decimal Maas { get; set; }
 public decimal ZamOrani { get; set; }

public abstract decimal MaasHesapla();
 }

class Uzman : Personel
 {
 public decimal Prim { get; set; }
 public override decimal MaasHesapla()
 {
 return Maas + Prim + (Maas*ZamOrani);
 }
 }

class Lider : Personel {
 
 public override decimal MaasHesapla()
 {
 return Maas + (Maas * ZamOrani);
 }
 }

Main metodu ve çıktı aşağıdaki gibi olacaktır.


class Program
 {
 static void Main(string[] args)
 {
 var uzman = new Uzman()
 {
 Maas = 100,
 ZamOrani = 0.25m,
 Prim=200
 };
 Console.WriteLine("Uzman Maaşı: {0}", uzman.MaasHesapla());

var lider= new Lider()
 {
 Maas = 350,
 ZamOrani = 0.30m
 };
 Console.WriteLine("Lider maaşı: {0}", lider.MaasHesapla());
 Console.ReadKey();
 }
 }

capture2

Konuyu özetlersek;

  1. Abstract sınıflardan nesne örneklenemez.
  2. Abstract bir sınıf hem abstract hem de abstract olmayan üyeler barındırabilir.
  3. Abstract üyelerin gövdeleri child sınıflarda kodlanmak zorundadır.
  4. Abstract sınıflar içerisinde abstract üyelerin gövdeleri kodlanamaz. Sadece tanımları yapılır.
  5. Abstract anahtar kelimesi hem metodlar için hem de classlar için kullanılabilir.
  6. Bir metodun abstract olarak işaretlenebilmesi için sınıfının da abstract olarak işaretlenmiş olması gerekmektedir.

Sealed Kavramı

Abstract yapısına değinmişken sealed kavramından da bahsetmek istedim.

  1. Sealed anahtar kelimesini sınıf adının yanına yazdığınızda artık o sınıftan başka sınıflar türetilemez.
  2. Eğer bir metod adının yanına yazılırsa o metod child sınıflarda override edilemez olacaktır.

Sealed yapılmak istenen metod ana sınıfın metodu ise sealed anahtar kelimesini kullanamazsınız. Çünkü zaten bunu yapmak için o metodu virtual ya da abstract yapmamanız yeterlidir.

İyi Çalışmalar.

Trigger Mimarisi (DDL Trigger)

Tüm veritabanı hatta daha geniş kapsamlı olarak Server bazında gerçekleştirilen Create, Alter Drop gibi ddl (Data Definition Language) işlemlerini kontrol etmek istediğimiz durumlarda DDL trigger’larını kullanabiliriz.Sadece veritabanı üzerindeki ddl işlemlerini kontrol etmek için DDL trigger’ımızı ilgili veritabanı üzerinde oluşturmamız gerekir. Server seviyesinde ddl işlemleri için trigger’ımızı master veritabanında oluşturmalıyız. Ddl trigger’larının tetiklenmesi ile oluşan event hakkında bize bilgi veren EVENTDATA() fonksiyonunu kullanabiliriz.

Veritabanı Seviyesinde DDL Trigger

Trigger’ımız tetiklendiğinde loglarımızı tutacak log tablomuzu oluşturalım ve ardından create_index ve drop_index komutları ile tetikleneceğini belirttiğimiz trigger’ımızı yazalım. Non-clustered indeksimizi yaratıp, eventdata çıktısını  trigger sayesinde log tablomuza insert ettik.


create table DdlEventData
(
EvenData xml not null,--kullanacağımız eventdata() fonksiyonun çıktısı için veri tipinin xml olması gerekmektedir.
ActionDate datetime not null default GETDATE()
)

create trigger Trg_IndexOperation
on Database
for CREATE_INDEX, DROP_INDEX
AS
SET NOCOUNT ON
INSERT DdlEventData
(EvenData) values (EVENTDATA())

create nonclustered Index IX_ContactID ON HumanResources.Employee(LoginID)

select EvenData from DdlEventData

Çıkan resultta çıkan linke tıklayıp gelen xmli açalım. Xml oldukça detaylı bilgi içermektedir. ServerName, LoginName,DatabaseName gibi alanları göreceksiniz.

Server Seviyesinde DDL Triggerlar

Create login, create database gibi ddl komutlar server seviyesinde event’lerdir. Server seviyesinde ki ddl triggler’larımızda dml trigger’larımız gibi eventdata() fonksiyonunu kullanarak aynı şekilde detaylı bilgi elde edilebilir.Örnek olarak bir tane logon trigger yazalım. Logon triggler’lar sql servere login olduğunda tetiklenen triggler’lardır.

Örneğimizde barısy adlı kullanıcımız sabah 9 ile akşam 6 arasında sql servere bağlanma izni vermeyelim.


create login barisy with password='1234'

create trigger Trg_Logon_Control
on all server
for logon
as
begin
if ORIGINAL_LOGIN()='barisy' and DATEPART(hh,getdate()) between 9 and 16
begin
rollback
end
end

Trigger’ımızı yazdıktan sonra barisy adlı kullanıcımızın mesai saatlerinde login olamadığını göreceksiniz.

Triggler’ların Yönetilmesi

Nested trigger kavramı bir trigger tetiklendikten sonra yaptığı işlemin başka bir trigger’ı tetiklemesi ve ikinci tetiklenen trigger’ın başka bir trigger’ı tetiklemesi ve bu zincirin böyle artarak uzaması anlamına gelmektedir. Sql server böyle sonsuz döngü oluşturabilecek durumları önlemek için iç içe çağırılabilecek trigger sayısını en fazla 32 olarak sınırlandırmıştır. Eğer iç içe trigger kullanmayacaksak sql server özelliker advanced tabında allow triggers to fire others seçeneğini false yapmalıyız.

Recursive trigger yani tetiklenen triggerın yine kendini tetiklenmesiye oluşan yapıdır. Bu da sql server tarafından 32 ile sınırlandırılmıştır.

Bir tablo üzerinde herhangi bir dml işlemi ile tetiklenen birden fazla trigger varsa bunları çalışma sırasını ayarlayabiliriz.Böyle 3 tane triggerımız var diyelim sp_settriggerorder adlı sistem sp’si ile ilk çalışmasını istediğimiz triggera first, son triggerımıza ise last diyerek belirli bir sıraya koyabiliriz. 3 ten fazla ise trigger’larımız çalışma sırasını garanti edemeyiz.


sp_settriggerorder 'trigger_name','First','process type (insert,delete)'
sp_settriggerorder 'trigger_name','Last','process type (insert,delete)'

İyi çalışmalar.

Trigger Mimarisi (DML Trigger-2)

Instead Of Dml Trigger

İnstead of Dml trigger’ları tetiklendiğinde dml komutları yerine sadece trigger tetiklenir. Bir insert işlemi için instead of trigger’ı oluşturduğumuzda tablo üzerinde insert işlemi yapmak istediğimizde insert işlemi yapılmaz bunun yerine instead of dml trigger’ımız tetiklenir. Bu yöntem tablo üzerinde yapılacak olan dml işlemlerini kontrol etmek için kullanılır. After dml triggerlar ile instead of dml triggerlar arasındaki en büyük fark; after dml triggerlar sadece tablolar üzerinde tanımlanırken, instead of dml triggerlar hem tablo hem de view üzerinde tanımlanabilmektedir.

Şimdi bir tane instead of dml trigger yazalım. Yapılan insert işleminde girilen tarih eğer şimdik tarih değerinden küçükse verinin girilmesini engelleyip uyarı mesajı verelim.Bunun için Adventureworks veritabanında person.emailadress tablosunu kullanalım.


create trigger Person.Trg_AdressDateControl
on Person.EmailAddress
instead of insert
as
set nocount on
declare @ModifiedDate Datetime
select @ModifiedDate=ModifiedDate from inserted
if(@ModifiedDate < GETDATE())
begin
print 'Eski Tarihli Kayıt Eklenemez'
rollback
end
else
begin
insert into Person.EmailAddress (BusinessEntityID,EmailAddressID,EmailAddress,rowguid,ModifiedDate)
select BusinessEntityID,EmailAddressID,EmailAddress,rowguid,ModifiedDate from inserted
end

insert into person.EmailAddress (EmailAddress,rowguid,ModifiedDate)
values ('hayric@gmail.com',NEWID(),DATEADD(day,-1,getdate()))

Dikkat edilecek olursa rollback ile işlemi geri alabildik bunun sebebi triggerın tetiklenmesiyle sql server otomatik olarak bir tane transaction başlatır. Bu transactionın rollback olması ise yapmak istediğimiz dml işlemini iptal eder.

Update Fonksiyonu

Şimdiye kadar insert ve delete dml işlemleri ile alakalı trigger hazırladık. Update işlemi için özel bir sanal tabloumuz yoktur, update işlemi önce verinin silinip (deleted) sonra eklenmesi (inserted) olarak tanımlandığı için özel bir tablosu yoktur. Biz deleted tablosu ve inserted tablosundaki verileri karşılaştırmak yerine sql serverin update() fonksiyonunu kullanacağız.

Update fonksiyonu parametrek olarak kolon adı alır ve eğer o kolon güncellenmiş ise geriye true değerini döndürür. Bir tane update triggerı yazalım. Bu sefer tablomuz Person.CountryRegion tablosudur.


create trigger Person.Trg_CountryRegionCodeControl
on Person.CountryRegion
after update
as
if update(CountryRegionCode)
begin
print 'Ülke Kodu Değiştirilemez'
end

update Person.CountryRegion set CountryRegionCode='BIX' where Name = 'Andorra'

İşlemimiz tiggerımıza takılıp rollback yapılmıştır. Böylece tablolarımızda update edilmesini istemediğimiz alanları kontrol edebiliriz.

İyi çalışmalar.

Trigger Mimarisi (Dml Trigger-1)

Sql Server gibi ilişkisel veritabanı yönetim sistemlerinde tablo veya view üzerinde gerçekleştirilen insert,update, delete gibi DML işlemlerini ve veritabanı veya server seviyesinde create,alter, veya drop gibi DDL işlmelerinden biri gerçekleştiğinde tetiklenip çalışacak olan Sql Server objelerine trigger denir. DML ve DDL işlemlerinin performanslarını takip edilirken triggler mutlaka kontrol edilmelidir. Yapacağımız işlem uzun zaman alıyorsa buna ilgili tabloda yada server üzerinde tanımladığımız trigger sebep olabilir.

DML TRİGGER

Dml triggerları sadece yenir bir kayıt eklendiğinde, silindiğinde yada güncelleme yapıldığında çalışır. Triggerların hangi işlem den sonra tetikleneceğini bizim belirtmemiz gerekir. Tüm dml işlemlerini içeren tek bir trigger yazabiliriz.

DML trigger’ları da kendi içinde AFTER ve INSTEAD OF trigger’ları olmak üzere ikiye ayrılmaktadır. After triggerları sadece tablo üzerinde tanımlanan ve DML işlemi gerçekleşip bittikten sonra çalışan kod bloklarıdır. Instead of trigglerları ise hem tablo hemde view’ler üzerinde gerçekleştirilen DML işlemleri yerine çalışacak olan kod bloklarıdır.Instead of triggerları hem istediğimiz tablo yada view’ler üzerinde bize yetkinlik sağlayacaktır. İstediğimiz tablo yada view’lere uygulama aracılığıyla işlem yapılmasını istemiyorsak ama eğer gerçekten gerekliyse bu tip trigger sayesinde bizim kontrolümüzde gerçekleşebilir.

Trigger yazarken dikkat etmemiz gereken noktalar: 

— Oluşturduğumuz trigger’ı tetikleyen müdahale edebileceğimiz bir kod ise trigger kullanmak yerine bu işlemi t-sql kodu ile yapmalıyız.

— Non-Logged yani log’lanmayan işlemlerin yapılması trigger’ları tetiklemez. Truncate table writetext gibi komutlar trigger’ların tetiklenmesi sağlamaz.

— Trigger’lar ilişkili oldukları tablo üzerinde gerçekleştirilen dml işlemlerinin performansını da etkileyecektir. Yani insert, update, delete gibi dml işlemleri ile tetiklenecek dolayısıyla dml işlemlerinin süresi uzayacaktır. Eğer gereksiz ise trigger kullanılmamalıdır.

After DML Trigger 

Bit tablo üzerinde gerçkleştirilen insert, update, delete gibi dml işlemlerinin başarılı bir şekilde tamamlanmasıyla otomatik olarak tetiklenen trigger’lara after dml trigger denmektedir.

Triggerların tetiklenmesini sağlayacak, dml triggerları tetiklendiğinde sql server tarafından otomatik olarak oluşturulan deleted ve inserted adlı iki tane sanal tablo oluşur. Bu tablolar trigger’ımızın tetiklenmesini sağlayacak olan işlem sonucunda etkilenen kaydın bilgisini tutmaktadır. Herhangi bir tabloya insert yapıldığında inserted sanal tablosunda kaydın bir kopyası tutulmaktadır. Aynı şekilde bir kayıt silindiğinde deleted adlı sanal tabloda silinen kaydın bir kopyası tutulmaktadır. Update dml işlemi için ise updated tablosu yerine inserted ve deleted (update edilen bir kayıt önce silinip sonra yeni hali eklenir.) tablolarına olmak üzere iki kayıt atılmaktadır.

AdventureWorks 2012, production.produc tablosu için  delete ve insert loglarımızı tutacağımız tabloyu oluşturalım.Tablodaki 3 alana ek olarak operation adlı bir alan (ne işlemi yapıldığı bilgisini tutacak) ve modifieddate (işlemin ne zaman yapıldığını tutacak) adlı ikinci alanı log tablomuza ekledik.

AdventureWorks 2012 databaseinde Product şemasın altındaki ProductInventory tablosu için log tablomuzu ve triggerımızı  oluşturduk


create table Production.ProductInventoryLog
(
 Operation varchar (10) not null,
 ProductID int not null,
 LocationID int not null,
 Shelf varchar (10) not null,
 Bin tinyint not null,
 Quantity smallint not null,
 rowguid uniqueidentifier not null,
 ModifiedDate datetime not null
)

 

create trigger Production.Trg_ProductInventoryLog
on Production.ProductInventory
after insert,delete
as
begin
set nocount on
--insert işlemlerinin loglanması
insert Production.ProductInventoryLog
(Operation,ProductID,LocationID,Shelf,Bin,Quantity,rowguid,ModifiedDate)
select distinct 'Insert', i.ProductID, i.LocationID,i.Shelf,
i.Bin, i.Quantity, i.rowguid,GETDATE()
from inserted i

--delete işlemlerinin loglanması
insert Production.ProductInventoryLog
(Operation,ProductID,LocationID,Shelf,Bin,Quantity,rowguid,ModifiedDate)
select distinct 'Insert', d.ProductID, d.LocationID,d.Shelf,
d.Bin, d.Quantity, d.rowguid,GETDATE()
from deleted d

end

 İnsert ve delete sorgularımızı yazıp, log tablomuza bakalım.

insert Production.ProductInventory (ProductID,LocationID,Shelf,Bin,Quantity) values (317,6,'A',4,22)
delete Production.ProductInventory where ProductID=317 and LocationID=6
select * from Production.ProductInventoryLog

İyi çalışmalar.

Kullanıcı Tanımlı Fonksiyonlar

UDF (User Defined Function), kullanıcı tanımlı fonksiyonlar birden fazla t-sql ifadesinin art arda işlenip bir sonuç üretilmesi için kullanılan sql server objeleridir diyebiliriz. Stored procedure’lerden farklı olarak kullanıcı tanımlı fonksiyonlar içinde DML işlemleri yapamayız. Fonksiyonlarda sorgu sonucunda gelen değer kullanıcıya döndürebiliriz.

–Sp’lerden dönen değer spnin doğru çalışıp çalışmadığını kontrol ederken, fonksiyonlardan dönen değeleri başka bir işlem de kullanabiliriz.

–Spler içinde RAISEERROR veya @@ERROR gibi yapılar ile hata kontrolü yapabiliyorduk, ama kullanıcı tanımlı fonksiyonlarda bu şekilde hata kontrolü yapılamaz.

–Fonksiyonların içinde geçici tabloları kullanamayız ve splerde olan output parametresi kullanıcı tanımlı fonksiyonlarada yoktur bunun yerine return type adlı yapıyı kullanabiliriz.

Scalar değer döndüren fonksiyonlar

Bir veya daha fazla parametre alıp basit bir değer döndüren fonksiyonlardır. Bu gibi fonksiyonlar matematiksel işlem yapan fonksiyonlar oduğu gibi daha kompleks yapı içeren fonksiyonlar da olabilir.

create function dbo.ConcatPerson
(@emailpromotion int)
returns varchar (max)as
begin
declare @name varchar(max);
set @name='';
select @name= @name + FirstName +',' from Person.Person
where EmailPromotion=@emailpromotion;
return @name;
end
select dbo.ConcatPerson (2)

Yukarıdaki scalar fonksiyon AdwentureWorks database’inde person.person tablosunda emailpromotion değeri 2 olan firstname ler arasına virgül koyarak listelemiştir ve geri değeri döndürmüştür.

Tablo Değer Döndüren Fonksiyonlar

Geriye scalar değer değil tablo şeklinde bir sonuç seti döndüren fonksiyonlardır.

Inline table-valued fonksiyonlar

Bu fonksiyonların sonuçları sanki bir tabloymuş gibi sorgulanabilir. Viewlere benzesede viewlerde parametre geçemeyeceğimiz için ayrışırlar. Bu fonksiyonları parametrik viewler olarak da adlandırabiliriz. Herhangi bir hesaplama yapmayacağımız için begin ve end blokları arasına kodumuzu yazmamıza gerek yoktur.


create function dbo.PersonDetails (@businessId int)
returns table
as
return
(select * from Person.Person where BusinessEntityID=@businessId )
go
select * from PersonDetails (5)

Fonksiyonumuz bize verdiğimiz değerlere göre bir table döndürmüştür. Sonuç olarak gelen table resultı başka tablolarla join edebiliriz. Bunun için cross apply ve outer apply komutlarını kullanabiliriz.

Cross apply komutu ile inner join  yaparken outer apply komutu left outer join yapmaktadır.

Multi Statement Table-Valued Fonksiyonlar

İnline Table Valued fonksiyonlardan farkı sadece bir sorgu sonucunun döndürmek yerine daha kompleks işlemler yapıp bu işlemler sonucunda çıktıyı tablo şeklinde geriye döndürmektir.

create Function FunctionName(Input parametreleri)

returns @TableName Table (kolon listesi)

as

begin

table tipli değişkenin doldurulması için işlemler

return

end

Multi-Statement Table-Valued fonksiyonlara verilen tablo tipli değişkenler doldurulurken tempdb sistem veritabanını kullanılır. Çok fazla kaydın döndüreleceği durumlarda multi-statement table-valued fonksiyon kullanmak performans sorunu yaratabilir.

Önemli Kavramlar

Deterministik Fonksiyon: Fonksiyon aynı parametrelerle her çağırıldığında aynı sonucu üretmeyi garanti eden fonksiyonlardır. 4 işlem içeren fonksiyonları bunlara örnek verebiliriz. Aynı parametreler verildiğinde her zaman aynı sonucu döndüreceklerdir.

Nondeterministik Fonksiyon: Farklı zamanlarda aynı parametrelerle çağırıldığında farklı sonuç veren fonksiyonlardır.GETDATE(), RAND(),NEWID() fonksiyonları bu tip fonksiyonlara örnek olarak verilebilir.

Schema Binding Kavramı

Fonksiyonumuzu oluştururken with schema binding komutu ile oluşturursak, fonksiyonumuz içinde kullanılan tüm tablolar şema değişikliklerine karşı korunmuş olur. İlgili tablolara yeni bir kolon eklenebilmesine rağmen, var olanlar değiştirilemezler.


create function FuncName(Input)

returns Datatype

with schema binding

as

begin

fonksiyon kodu

return expression;

end

 

İyi çalışmalar…