LINQ’e Genel Bakış
LINQ nedir? :
LINQ, program ve veri arasındaki ilişkiyi başka bir boyuta taşıyan devrimsel bir programlama metodolojisidir. LINQ, bir programalama arayüzü sunar. C# diline getirdiği eklentilerle SQL benzeri tek bir söz dizimi ile farklı tiplerdeki verilerin sorgulanmasını sağlar. C# ile sorgu yazmak, tip güvenli çalışma, ifadelerin otomatik olarak tamamlanması ve IntelliSense gibi özelliklerle geliştiricinin üretkenliğini artırmayı sağlar.
Farklı LINQ uygulamaları mevcuttur. Bunlardan en temel olanlar şöyle özetlenebilir.
• Bellek üzerindeki nesnelerden oluşan koleksiyonları sorgulamak (LINQ to Objects),
• SQL Server veritabanlarındaki tabloları sorgulamak (LINQ to SQL),
• ADO.NET DataSet tiplerini sorgulamak (LINQ to DataSets),
• Xml verilerini sorgulamak (LINQ to XML)
• ADO.NET Entity Framework tarafından oluşturulan varlıkları sorgulamak (LINQ to Entity)
Bu sayılan LINQ uygulamaları aynı söz dizimini kullanır ancak farklı veri kümelerini hedeflemektedir.
Çoğu SQL komutlarına benzer yada aynı olan C# anahtar kelimelerinden oluşan ifadeler ile .NET koleksiyonlarına LINQ sorguları yazarsın. Bu anahtar kelimeler, LINQ Standart Sorgu Operatörleri olarak anılır (LINQ Standart Query Operators). Bu operatörler, System.Linq isim alanı altında yer alan Enumerable sınıf tarafından tanımlanır. C# derleyicisi (compiler), sorgu ifadelerini, çalıştırılmak üzere CIL ortak ara diline dönüştürür.
Aşağıda temel bir LINQ sorgusunun genel yapısını inceleyebilirsin :
var sorgu = from eleman in koleksiyon
where kriterler
orderby sıralamaKıstası [ascending|descending]
select [takmaAd = ]kolonIfadesi [ , [takmaAd2 = ] kolonIfadesi2]
var anahtar kelimesi, yukarıdaki LINQ sorgu sonucunun atandığı sorgu isimli lokal değişken için tip çıkarsaması yapar. var, bir veri tipi değildir. var, eşitliğin sağ tarafındaki veriye ait tipin, derleyici tarafından otomatik olarak tespit edilmesini ve CIL koduna yazılmasını sağlar.
LINQ Sorguları :
Bir LINQ sorgusu, veri kaynağından istenen veriyi elde etmek için kullanılır. Bir LINQ sorgu süreci 3 ayrı parçadan oluşmaktadır.
- Veri kaynağını elde et.
- Sorguyu oluştur.
- Sorguyu çalıştır.
Aşağıdaki örneği bu ayrımı göz önünde bulundurularak incele. Örnekte veri kaynağı olarak bir int dizisi kullanılmıştır.
// Veri Kaynağı
int[] sayilar = new int[5] { 1, 2, 3, 4, 5 };
//Sorgu Oluşturma
var tekSayilar = from s in sayilar where (s % 2) == 1 select s;
// Sorgunun Çalıştırılması.
foreach (int s in tekSayilar)
{
Console.WriteLine(s);
}
LINQ mimarisinde sorgunun çalıştırılması, sorgunun kendisinden ayrı bir süreçtir. Yani sorgu oluşturulurken, veri elde edilmez.
Veri Kaynağı
Örnekteki veri kaynağı int dizisi, IEnumerable<T> generic tipini desteklemektedir. Dolayısıyla sorgulanabilir bir tiptir. Bir sorgu foreach döngüsü ile çalıştırılır ve foreach IEnumerable yada IEnumerable<T> arayüzlerini uygulamış tiplere ihtiyaç duyar.
Sorgu
Sorgu, veri kaynağı yada kaynaklarından hangi bilgilerin elde edileceğini belirtir. Dilenirse bir sorgu aynı zamanda veri kaynağından elde edilen bilginin nasıl sıralanacağını, gruplanacağını ve elde edilmeden önce nasıl bir forma gireceğini belirtebilir. Bir sorgu, sorgu değişkeninde saklanır. Burada önemli olan nokta, sorguyu saklayan değişken tek başına her hangi bir aksiyon alamaz ve üzerinde veri saklamaz. Sadece ileri bir noktada sorgu çalıştırıldığında sonucu üretmek için gereken bilgileri saklar.
Sorgunun Çalıştırılması
Sorgunun çalıştırması, sorguyu saklayan değişkenin foreach döngüsüne sokulması ile gerçekleşir. Bu konsepte ertelenmiş çalıştırma (deffered execution) adı verilir.
Örnekte foreach döngüsü ile sorgu kullanıldığında, sorgu çalıştırılmış ve veri kaynağında yer alan tek sayılar filtrelenmiştir.
Bir sorgunun hemen çalıştırılması ve sonuç kümesinin elde edilmesi istenirse sorgu cümleleri ToList() yada ToArray() metotları ile birlikte kullanılabilir.
List<int> tekSayilar = (from s in sayilar where (s % 2) == 1
select s).ToList();
Bu durumda hem sorgu cümlesinin hemen çalıştırtılması sağlanır; hem de sonuç kümesi bellekte bir koleksiyon üzerinde saklanmış olur.
Generic Koleksiyonlar İle LINQ :
LINQ sorgu değişkenleri IEnumerable<T> arayüzünden türeyen bir tip olmalıdır. Örneğin IEnumerable<Ogrenci> tipinde bir sorgu değişkeni gördüğün zaman şu anlama gelir : Bu sorgu çalıştırıldığında sıfır yada daha fazla sayıda Ogrenci sınıfından nesne üretecektir.
Örnek için öncelikle basit bir Ogrenci sınıfı kodla.
public class Ogrenci
{
public string Adi;
public DateTime DogumTarihi;
public int Numarasi;
public Ogrenci(string adi, DateTime dogumTarihi, int numarasi)
{
this.Adi = adi;
this.DogumTarihi = dogumTarihi;
this.Numarasi = numarasi;
}
}
Main metodu içerisine 4 tane Ogrenci nesnesi içeren bir List<T> koleksiyonu oluştur.
Ogrenci o1 = new Ogrenci(“Ali Yılmaz”,new DateTime(1982, 2 ,2),7823482); Ogrenci o2 = new Ogrenci(“Kerim Ak”, new DateTime(1975, 10, 23), 9288763); Ogrenci o3 = new Ogrenci(“Can Çelik”, new DateTime(1990, 7, 30), 1372426); Ogrenci o4 = new Ogrenci(“Halil Ata”, new DateTime(1987, 1, 13), 9126343);
List<Ogrenci> ogrenciler = new List<Ogrenci>() { o1, o2, o3, o4 };
Şimdi LINQ sorgusu ile bu koleksiyonda arama yapalım : 1985 yılı öncesinde doğan öğrencilerin isimleri ve numaraları, aralarında “/” işareti olacak şekilde yan yana tek bir string tipli veri olarak elde edilsin. Aşağıdaki sorgu bu işi görecektir :
var sorgu = from o in ogrenciler
where o.DogumTarihi.Year <= 1985
select o.Adi + ” / ” + o.Numarasi.ToString();
Bu sorgu cümlesinde dikkat çekmesi gereken 1-2 önemli nokta vardır. Birincisi generic koleksiyon üzerinden LINQ sorgusu yazarken Visual Studio editörünün otomatik kod tamamlayıcı özelliği Intellisense sana yardım eder. Örneğin where operatöründen sonra “o.” yazdığında Ogrenci sınıfının public üyeleri listelenir ve sen filtrelemede kullanmak istediğin DogumTarihi alanını listeden seçebilirsin. Böylece hızlı ve minimum hatayla sorgu yazarsın.
Sorgu sonucunu görmek için sorguyu çalıştırman lazım; dolayısıyla foreach döngüsü yazman gerekiyor.
foreach (var o in sorgu)
{
Console.WriteLine(o);
}
Kod parçası çalıştırıldığında aşağıdaki çıktı elde edilir :
var An ah ta r Kelim e si : Der ley i cinin Tip Çıka rsa ması Yap ma sı
Örnekte hem sorgu değişkeni olarak hem de foreach döngüsünde döngü değişkeni olarak var anahtar kelimesi kullanılmıştır.
sorgu isimli sonuç kümesi değişkeni incelendiğinde, generic koleksiyonun içerdiği nesnelerin veri tipine bakarak çıkarsama yapılır ve CIL koduna veri tipi olarak IEnumerable<Ogrenci> yazılır.
Benzer şekilde foreach döngüsünde var ile tanımlanan döngü değişkeninin veri tipi, derleyici tarafından sorgu değişkeninin elemanlarından tespit edilir. Böylece çalışma zamanında işlemler Ogrenci tipinde gerçekleştirilir.
LINQ to SQL Dizayn Ekranı :
LINQ to SQL Classes, Visual Studio ile açılan bir C# projesindeki şablonun adıdır.
Projene yeni bir LINQ to SQL Classes eklediğinde, boş bir O/R dizayn ekranı açılır. Bu, veritabanından ekleyeceğin sql nesneleri ile ilgili metadataları tutan xml tabanlı ve dbml uzantılı bir dosyadır.
Soldaki büyük alana tablo ve view, soldaki küçük alana ise stored procedure ve fonksiyon ekleyebilirsin. Veritabanına bağlanıp sql nesnelerini görüntülemeni sağalayacak araç ise “Server Explorer”dır. Bu araca menüden View à Server Explorer sekmelerinden erişebilirsin. Bu araçta yer alan Data Connections kalemine sağ tıklayıp Add Connection seçilerek bağlanılacak veritabanı ile ilgili bilgiler girilebilir. Sonucunda veritabanındaki bütün sql nesneleri önüne listelenecektir. Yapman gereken Server Explorer aracındaki listeden dizayn ekranına ihtiyaç duyduğun tabloları sürükleyip bırakmak. Bu konudaki örneklerde, daha önce SQL konusunda kullandığın Adventureworks veritabanını kullanabilirsin.
Entity olarak dizayn ekranı tarafından üretilen sınıfları incelemek istersen Solution Explorer penceresinde *.designer.cs dosyasına bakmalısın.
Nesneleri Ekleme:
LINQ to SQL, projene veri erişim katmanı uygulamak için çok mükemmel bir adaydır. LINQ to SQL ile kullanacağın merkezi nesne DataContext sınıfıdır. Bu sınıf, veritabanları ile uygulama arasındaki bağlantıyı yönetir. Aynı zamanda ürettiğin entity sınıflarına ait veritabanındaki kayıtları içeren Table<TEntity> tipinde koleksiyonları saklar. Bu koleksiyonlar sayesinde kod tarafında, bir tablodaki bütün verilere kolayca erişebilir ve ASP.NET gibi bir arayüzde gösterebilirsin. Örneğin Musteris koleksiyonunu ele alalım. Bu, içinde Musteri tipinde nesneler barındıran Table<Musteri> tipinde bir koleksiyondur.
Veritabanına doğru çalıştırılacak bir LINQ sorgusu hazırlamak için Table<TEntity> tipindeki koleksiyon ile çalışmalısın. Bu nesne üzerinden gerçekleştirdiğin manipülasyonlar, C# derleyicisi tarafından önce dinamik T- SQL sorgularına dönüştürülür; ardından veritabanına doğru bağlantı açılarak çalıştırılır. DataContext, SqlClient sağlayıcısı üzerinden veritabanına bağlantı açılması ve kapatılması süreçlerini otomatik olarak yönetir. Senin ekstra kod yazmana gerek yoktur.
Yeni Kayıt Eklemek
LINQ to SQL kullanarak veritabanındaki bir tabloya yeni kayıt eklemek için Table<TEntity> koleksiyonuna yeni bir kayıt eklemen yeterlidir. Bu kayıt, veri eklemek istediğin tablo için dizayn ekranından ürettiğin entity sınıfı tipinde olmalıdır.
DataContext.TabloAdı.Add(YeniNesne)
Nesneyi hazırlayıp koleksiyona ekledikten sonra DataContext.SubmitChanges() metodu çağrılır. Bu metot, yeni nesnenin veritabanında saklanması için gerekli T-SQL insert sorgusunun hazırlanması ve çalıştırılmasını sağlar.
Ayrıca bütün veritabanı işlemlerinde mutlaka System.Data.Linq.DataContext tipinden türeyen projene özel DataContext nesnesine ihtiyacın vardır.
NesneModeliDataContext ctx = new NesneModeliDataContext(); Department yeniDep = new Department();
yeniDep.Name = “Yazılım”; yeniDep.GroupName = “Bilgi Teknolojileri”; yeniDep.ModifiedDate = DateTime.Now;
ctx.Departments.InsertOnSubmit(yeniDep);
ctx.SubmitChanges();
Dilenirse InsertOnSubmit metodu yerine InsertAllOnSubmit metodu kullanılarak bir kayıt değil önceden hazırlanmış bir koleksiyon içindeki bütün kayıtların eklenmesi sağlanabilir.
Nesne Güncelleme:
Kayıt güncellemek için DataContext üzerindeki koleksiyondan güncellenmesi istenen bir yada birden fazla nesnenin elde edilmesi gerekir. Bunun için where ifadesi ile filtrelenmiş bir sorgu yazılması gerekir. Sorgu sonucunda elde edilen nesnelerin istenen özelliklerinde (property) gerekli değişiklikler yapıldıktan sonra DataContext.SubmitChanges() metodu çağrılır. Bu metot kayıtların veritabanında güncellenmesi için gerekli T- SQL update sorgusunun hazırlanması ve çalıştırılmasını sağlar.
NesneModeliDataContext ctx = new NesneModeliDataContext(); Department depGuncellenecek = (from d in ctx.Departments
where d.DepartmentID == 1
selectd).SingleOrDefault();
if (depGuncellenecek != null)
{
depGuncellenecek.GroupName = “Finansal Yönetim”;
ctx.SubmitChanges();
}
Bu örnekte DepartmentId değeri 1 olan tek bir kayıt olacağı için sorgu sonucunda SingleOrDefault metodu çağrılmıştır. Böylece eşitliğin sol tarafında tek bir Department nesnesi elde edilmiş ve o nesnenin GroupName kolonu güncellenmiştir. Eğer birden fazla nesne üzerinde aynı anda güncelleme yapmak durumunda kalırsan aşağıdaki kod parçası sana yol gösterici olacaktır.
NesneModeliDataContext ctx = new NesneModeliDataContext();
var depGuncellenecek = from d in ctx.Departments where d.DepartmentID < 10
select d;
foreach (var d in depGuncellenecek)
{
d.GroupName = “Finansal Yönetim”;
}
ctx.SubmitChanges();
Bu kod parçasında LINQ sorgusu sonucunda 9 kayıt döner. Bu 9 kayıt üzerinde güncelleme yapmak için foreach döngüsünden faydalanılır. Döngünün sonunda SubmitChanges metodu çağrılarak bütün güncellemeler tek bir seferde veritabanına yansıtılmış olur.
Veri tabanından tek bir kayıt yada birden fazla kayıt silmek için DataContext üzerindeki koleksiyondan nesnelerin silinmesi gerekir.
DataContext.TabloAdı.Remove(SilinecekNesne)
Bunun için where ifadesi ile filtrelenmiş bir sorgu yazılması gerekir. Sorgu sonucunda elde edilen nesneler koleksiyondan silindikten sonra DataContext.SubmitChanges() metodu çağrılır. Bu metot kayıtların veritabanından silinmesi için gerekli T-SQL delete sorgusunun hazırlanması ve çalıştırılmasını sağlar.
NesneModeliDataContext ctx = new NesneModeliDataContext();
var depSilinecek = (from d in ctx.Departments where d.DepartmentID == 3
select d).SingleOrDefault();
if (depSilinecek != null)
{
ctx.Departments.DeleteOnSubmit(depSilinecek);
ctx.SubmitChanges();
}
Dilenirse DeleteOnSubmit metodu yerine DeleteAllOnSubmit metodu kullanılarak bir kayıt değil bir koleksiyon içindeki bütün kayıtların silinmesi sağlanabilir.
Bu yazı www.acikakademi.com ders notlarından derlenmişdir.