Değerli Okurlarım Merhabalar,
Bildiğiniz gibi uzun bir süredir Microsoft LINQ (.Net Language Integrated Query) adını verdiği ve C# 3.0' ın amacı olan bir projeyi sürdürmekte. Projenin en büyük amacı, özellikle veri üzerinde yapılabilecek sorgulama tekniklerinin dahada yaygınlaştırılması ve dil ortamına entegre edilebilmesi. Örneğin LINQ sayesinde IEnumerable<T> arayüzünü (interface) uygulamış herhanbir tip (type) üzerinde sql sorgularına benzer ifadeler kullanabilir ve alt kümeler çekebiliriz.
LINQ' nun bu özelliklerini kullanabilmek için C# dilinin 3.0 versiyonunda pek çok yenilik bulunmaktadır. Örneğin var anahtar sözcüğü ile, tip belirtmeden değişken tanımlayabilmek (var keyword), nesne örneklerini oluştururken yapıcı metodlara gerek kalmadan başlangıç değerlerini belirleyebilmek (object initializing), isimsiz tipler oluşturabilmek (anonymous types) hatta lambda ( => ) isimli yeni bir operatör sayesinde isimsiz metodları bir adım ileriye götürebilmek vb... mümkündür. Ancak asıl etki az öncede bahsettiğimiz gibi LINQ ile gelmektedir.
LINQ' nun iki farklı uygulama alanı daha vardır. Bunlar XLINQ - XML Store and Language Integrated Query ve DLINQ - Database Language Integrated Queries' dur. XLINQ ile xml verileri üzerinde dil ile tümleştirilmiş sorgulama ifadeleri yazmak mümkün olabilmektedir. DLINQ ise bugünkü makalemizin asıl konusudur. DLINQ ilişkisel verilerin nesne olarak ifade edilebildiği ortamlar için dil destekli sorgular kullanabilmemizi sağlamaktadır. Buna göre, bir veritabanına ait herhangibir tabloyu uygulama tarafında sınıf olarak temsil ettiğimiz durumlarda (entity), bu sınıflara ait örneklerin çalışma zamanında veritabanından doldurulmasını sağlayabilir ve ilgili nesne örnekleri üzerinde LINQ ifadelerini kullanarak sorgular çalıştırabiliriz. Bunun tersi durumda geçerlidir. Yani nesnelerin bellekte işaret ettikleri içerikte değişiklik yapıp veritabanına doğru yönlendirilebilir. Konuyu daha iyi anlayabilmek için bir örnek üzerinden hareket edelim. Bu örneğimizde AdventureWorks içerisinde kendi oluşturduğumuz bir tabloyu ele alacağız. Kullanacağımız örnek tabloyu aşağıdaki script yardımıyla oluşturabilirsiniz.
USE [AdventureWorks]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Calisanlar](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Ad] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Soyad] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Maas] [money] NOT NULL,
[GirisTarihi] [datetime] NOT NULL,
[DogumTarihi] [datetime] NOT NULL,
[Unvan] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Departman] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
CONSTRAINT [PK_Calisanlar] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
DLINQ için kilit nokta DataContext isimli sınıftır. DataContext sınıfı çalışma zamanında, entity sınıfları ile asıl veri kaynağının haberleşebilmesine imkan tanır. Çoğunlukla DLINQ uygulamaları içerisinde, Strongly Typed DataContext sınıfları yer almaktadır. Bu sınıflar içlerinde, entity sınıflarına ait generic tipler kullanır. DataContext üzerinden türetme yardımıyla oluşturulan tipler sayesinde LINQ ifadelerini nesne örnekleri üzerinde çalıştırabiliriz. Aynı zamanda nesne örnekleri üzerinde yapılacak değişiklikleri veritabanı tarafına doğru gönderebiliriz. Bu bilgilerden yola çıkarak hareket ettiğimizde ilk yapmamız gereken aşağıdaki gibi bir entity sınıfı yazmak olacaktır. Bu sınıf temel olarak Calisanlar tablosundaki her bir satırı temsil edebilme yeteneğine sahip olacak şekilde tasarlanmalıdır.
[Table(Name="Calisanlar")]
class Calisan
{
[Column(Name="Id",Id=true)]
public int Id;
[Column(Name="Ad")]
public string Adi;
[Column(Name="Soyad")]
public string Soyadi;
[Column(Name="Maas")]
public decimal Maasi;
[Column()]
public DateTime GirisTarihi;
[Column()]
public DateTime DogumTarihi;
[Column(Name="Unvan")]
public string Unvani;
[Column(Name="Departman")]
public string Departmani;
}
DLINQ bir sınıfın, nesne örneği olduğunda veritabanındaki hangi tabloya karşılık geldiğini, alanlarının (fields) tablo üzerindeki hangi kolonlara (columns) denk düştüğünü ve temel özelliklerinin neler olacağını belirtmemize yarayan nitelikler(attributes) içermektedir. Örneğin yazmış olduğumuz Calisan isimli sınıfın, veritabanındaki hangi tabloya karşılık geldiğini Table isimli attribute yardımıyla belirleyebiliriz. Buna göre Calisan isimli sınıfımız Calisanlar isimli tabloya ait olacaktır.
|
Column, Table gibi nitelikler (attributes) ve biraz sonra göreceğimiz DataContext sınıfı, System.Data.DLinq isim alanı (namespace) altında yer almaktadır. |
Benzer şekilde sınıf içerisinde tanımlı alanların, Calisanlar tablosu üzerinde karşılık geldikleri kolonları Column isimli nitelik (attribute) yardımıyla belirlemekteyiz. Column niteliği yardımıyla eşleştirilecek kolon adının ne olacağını belirlemek dışında başka özellikleride ayarlayabiliriz. Örneğin Id isimli alanın aynı zamanda identity olacağını Id özelliğine true değeri atayarak belirlemekteyiz. Benzer şekilde bir alanın veri türünüde DBType özelliği ile Column niteliği içerisinde ayarlayabiliriz. Aşağıdaki şekilde Column niteliği içerisinde kullanabileceğimiz diğer özellikler görülmektedir.
Entity sınıfımızın ardından Strongly Typed DataContext sınıfını yazmamız gerekmektedir. Bu tip sınıflar genellikle bir veritabanı içerisindeki tabloların satırlarını temsil eden entity örneklerine ait toplu veri kümelerini barındırmak ve yönetmek amacıyla yazılmaktadır. Aşağıdaki kod parçasında yer alan AdventureWorks isimli sınıfın temel amacı budur.
using System;
using System.Data.DLinq;
namespace UsingDLINQ
{
class AdventureWorks:DataContext
{
public Table<Calisan> SirketCalisanlari;
public AdventureWorks(string conStr)
: base(conStr)
{
}
}
}
AdventureWorks isimli sınıfımız DataContext' ten türetilmektedir. Yapıcı metodumuz içerisinde dikkat edecek olursanız parametre olarak bağlantı bilgisini alıp DataContext sınıfının ilgili yapıcı metoduna (Constructor) göndermekteyiz. AdventureWorks isimli sınıfın bir diğer önemli üyesi SirketCalisanlari' dir. Bu üyenin tipi Table' dır. Table tipi burada generic' tir ve Calisan sınıfı tipinden örnekler barındırabileceği belirtilmektedir. Öyleyse biz SirketCalisanlari ile, Calisanlar tablosunun tüm satırlarını referans edebiliriz. Şimdi gelelim LINQ' yu işin içerisine katarak çalışma zamanında Calisanlar isimli tablodan nasıl sorgulama yapabileceğimize. Bu amaçla oluşturacağımız LINQ Preview Console uygulaması içerisinde aşağıdaki kodları yazmamız gerekiyor.
using System;
using System.Query;
namespace UsingDLINQ
{
class Program
{
static void Main(string[] args)
{
AdventureWorks adw = new AdventureWorks("data source=localhost;database=AdventureWorks;integrated security=SSPI");
var calisanListesi=from clsn in adw.SirketCalisanlari select clsn;
foreach(var c in calisanListesi)
{
Console.Write(c.Id.ToString());
Console.Write("\t"+c.Adi+" ");
Console.Write(c.Soyadi);
Console.Write("\t"+c.Maasi.ToString());
Console.Write("\t"+c.DogumTarihi.ToString());
Console.WriteLine("\t"+c.Departmani.ToString());
}
}
}
}
Uygulamamızı çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ederiz.
Dikkat ederseniz burada C# 3.0 ile gelen bir kaç yenilik yer almaktadır. Bunlardan birisi var anahtar sözcüğünün kullanılmasıdır. Bildiğiniz gibi var anahtar sözcüğü ile bir değişkeni, tipini belirtmeden tanımlayabilir ve kullanabiliriz. İkinci önemli yenilik ise C# 3.0 ' ın asıl konusunu oluşturuan LINQ ifadelerinin kullanılmasıdır. from anahtar sözcüğü ile başlayan ifademizde AdventureWorks sınıfına ait nesne örneği içerisinde yer alan Table tipindeki SirketCalisanlari nesne örneği üzerinden basit bir select sorgusu atılmaktadır. Burada yazım tarzı T-Sql göz önüne alındığında biraz ters gelebilir. Ancak uygulamacı gözüyle baktığımızda son derece mantlık bir sorgu cümlesi ortaya çıkmaktadır. Şimdi sorgumuzu biraz değiştirip aşağıdaki hale getirelim.
var calisanListesi=from clsn in adw.SirketCalisanlari where clsn.Departmani=="Yazılım" select clsn;
Bu durumda uygulamamızı çalıştırdığımızda sadece yazılım departmanına ait çalışanları elde edebiliriz.
|
Sorgu ifadelerinin doğru çalışabilmesi için (örneğin where anahtar sözcüğünü kullanabilmek için) System.Query isim alanını uygulamamıza dahil etmemiz gerekmektedir. |
Bu durumda sonuç şu şekilde olacaktır. Şimdi örneğimizi biraz daha geliştirelim ve DLINQ kullanarak ilişkili verilerin nesnel bazda nasıl sorgulanabileceğini ele almaya çalışalım. Bu amaçla Sql Server 2005 üzerinde bizim emektar Northwind veritabanını kullanacağız. Senaryo olarak Categories ve Products tablolarının birleştirilmiş hallerini (bir başka deyişle join edilmiş hallerini) ele almaya çalışacağız. Yanlız bu kez bu birleştirilmiş veri kümesini kod tarafındaki katmanlarda ele alacağız. Bizim için gerekenler öncelikli olarak Categories ve Products tabloları için birer entity sınıfı. Sonrasında ise yazmış olduğumuz bu entity sınıfları ile asıl tablolarımız arasındaki ilişkiyi sağlayacak DataContext' ten türemiş bir sınıf. DataContext tipinden türeteceğimiz bu sınıf içerisinde tablolar arasındaki ilişkiyide nesnel bazda sağlamamız gerekecektir. Bunun içinde Association niteliğinden ve EntitySet tipinden yararlanacağız. İlk olarak entity sınıflarımızı aşağıdaki gibi geliştirelim.
using System;
using System.Data.DLinq;
namespace UsingDLINQ
{
[Table(Name="Categories")]
class Kategori
{
[Column(Name="CategoryID",Id=true)]
public int KategoriId;
[Column(Name="CategoryName")]
public string KategoriAdi;
private EntitySet<Urun> m_urunDetay;
[Association(Storage="m_urunDetay",OtherKey="KategoriId")]
public EntitySet<Urun> UrunDetaylari
{
get { return m_urunDetay; }
set { m_urunDetay.Assign(value); }
}
}
[Table(Name="Products")]
class Urun
{
[Column(Name="ProductID",Id=true)]
public int UrunId;
[Column(Name="ProductName")]
public string UrunAdi;
[Column(Name="UnitPrice")]
public decimal BirimFiyat;
[Column(Name="CategoryID")]
public int KategoriId;
private EntityRef<Kategori> m_kategorisi;
[Association(Storage = "m_kategorisi", ThisKey = "KategoriId")]
public Kategori Kategorisi
{
get { return m_kategorisi.Entity; }
set { m_kategorisi.Entity = value; }
}
}
}
Buradaki kod parçasında, Kategori ve Urun sınıfları arasında çalışma zamanında ifade edilebilecek bire-çok (one-to-many) ilişkide tanımlanmıştır. Kategori sınıfı içerisinde bu işi UrunDetaylari isimli özellik gerçekleştirmektedir. Özelliğe uygulanan Association isimli nitelik, bir kategoriye bağlı ürünlerin nerede saklanacağını storage özelliği yardımıyla belirtmektedir. İkinci parametremiz ise bire-çok ilişki için gerekli anahtar(key) alanı işaret eder ki buda ilgili kategorinin id değeri olacaktır. Şimdide DataContext' ten türettiğimiz Strongly Typed tipimizi yazalım.
using System;
using System.Data.DLinq;
namespace UsingDLINQ
{
class Northwind:DataContext
{
public Table<Kategori> Kategoriler;
public Table<Urun> Urunler;
public Northwind(string conStr)
: base(conStr)
{
}
}
}
Dilerseniz daha fazla detaya girmeden nesnel bazda ifade edebileceğimiz bu ilişkiyi LINQ üzerinden nasıl kullanabileceğimize bakalım. Bu amaçla programımıza aşağıdaki kod satırlarını ekleyelim.
Northwind nrth=new Northwind("data source=localhost;database=Northwind;integrated security=SSPI");
var sonuclar = from urn in nrth.Urunler from ktg in nrth.Kategoriler where urn.KategoriId == ktg.KategoriId select urn;
var altSonuclar=from snc in sonuclar where snc.KategoriId==2 select snc;
Console.WriteLine("\t Join Üzerinden Belirli Kategori Altında Olanlar...");
Console.WriteLine();
foreach (Urun u in altSonuclar)
{
Console.WriteLine(u.KategoriId.ToString()+"\t"+u.Kategorisi.KategoriAdi+"\t"+u.UrunAdi+"\t"+u.BirimFiyat.ToString());
}
Uygulamamızı bu haliyle çalıştırdığımızda aşağıdaki ekran görüntüsünü elde ederiz. Gördüğünüz gibi KategoriId değeri 2 olan ürünleri çekebilmekteyiz. Bu amaçla iki adet LINQ ifadesi kullanılmıştır. İlk ifade aslında Urunler ve Kategoriler isimli Table nesne örneklerine ait veri kümelerini KategoriId alanları üzerinden birleştiren ve T-Sql deki Join benzeri bir işlevselliği sağlayan yapıdadır. İkinci LINQ ifadesinde ise bu Join yapısı üzerinden KategoriId' ye bir alt küme çekilmektedir.
Yapmış olduğumuz örneklerdende anlayabileceğiniz gibi DLINQ aslında iş katmanında yer alan tiplerin üzerinden T-Sql tarzında sorguların yapılabilmesini sağlamaktadır. DLINQ ana konu olarak LINQ ifadelerini kullandığından, çalışma zamanında bellek üzerinde bulunan nesneler üzerinde istenilen sorgulamalar yapılabilir. DLINQ kendi içerisinde kayıt düzenleme, transaction kullanımı gibi daha ileri seviye imkanlara da sahiptir. Bu konular ile ilgili daha detaylı bilgileri sitesinden bulabilirsiniz. Bu makalemizde kısaca DLINQ teknolojisine kısaca bakmaya ve tanımaya çalıştık. Makale içerisinde geçen bilgilerin çoğunun C# 3.0 ın son hali yayımlanana kadar değişiklik gösterebileceğini de göz önüne almamız gerektiğini unutmayalım. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.
Örnek Uygulama İçin Tıklayın.