15 Ocak 2009 Perşembe

NHibernate’e Başlama Klavuzu

Temeller

NHibernate kurumsal yazılım geliştirmede sıkça kullanılan açık kaynak kodlu bir Object-Relational Mapping(ORM) aracıdır.
ORM Nedir?
İlişkisel verileri, nesne tanımlarına dönüştürerek onlar üzerinde Nesneye Dayalı uygulama geliştirebilmemizi sağlayan araçlardır.
Bir ORM’in Üstlendiği Görevler Nelerdir?
· Temel Create-Read-Update-Delete fonksiyonlarını gerçekleştirebilmek için bir API
· Nesneler üzerinde sorgular oluşturabilmemiz için sağlanmış bir sorgu dili ya da bir API
· Mapping(Eşleme) tanımlarını yapabilmek için bir araç
· ORM gerçekleştirimi yapacak Transactional veri kalıcılığı, lazy-load ilişkilerin getirilmesi vb. işlemleri gerçekleştirecek bir framework…
Avantaj ve Dezavantajları
Avantajları:
  • Daha yüksek üretkenlik

    • Konsantrenizi CRUD işlemlerinden İş Mantığına yöneltebilmenizi sağlar.


  • Daha kolay bakım

    • Daha az satır kod, her zaman yazılımı daha anlaşılabilir kılar, daha önemlisi de daha kolay “refactor” edilir. Kodlar daha çok İş Mantığından oluşur.

  • Marka bağımsız Data Layer

    • ORM aracılığıyla yazılan DB erişim kodları sayesinde kolaylıkla farklı Database’ler üzerinde çalıştırılabilir.

Dezavantajları:
  • Performans
  • Çoğu zaman Stored Procedure ve View’lardan yavaş çalışır.
Not: Eğer “Yazılım İş Gücü Saat Bedeli > Lisans Ücreti+Donanım Bedeli” ise ORM araçlarını tercih etmek daha yaralı olacaktır.
Not 2: Performansı arttırmak için ORM araçları View’larla beraber kullanılabilir.

NHibernate

Session: Veritabanı’na bağlantı veri çekimi ve basit bir “Unit of Work” implementasyonu
ICreteria API: Tekrar kullanılabilir, hızlı sorgular üretmek için bir yöntem. LINQ’da kullanılan Extension Methods’a oldukça benze yapıda; ancak kolon isimleri vb. bilgiler stringler halinde verilmek zorunda kalınıyor.
HQL: String bazlı Object’ler üzerine de uygulanabilen bir sorgu dili.
Schema Mapping: Database’deki hangi tabloların hangi nesnelerle; hangi kolonların, hangi property’lerle; eşleşeceğini, Relation(Association)ların neler olduğunu belirttiğimiz hbm.xml dosyalarıdır.
Dialect: NHibernate’in hangi database dilini kullanması gerektiğini belirttiğimiz Database’e özel dil. Mesela biz burada Oracle9 Dialect’ini seçersek Oracle’a özel (9 ve üstünün desteklediği ek özellikleri de kullanabilen) dil sözlüğünü kullamasını söylemiş oluruz.

Genel yapı şekildeki gibidir.
1. NHibernate üzerinden HQL ya da ICreteria API’si sayesinde sorguda bulunuruz.
2. XML Schema dosyalarına ve NHibernate Configurasyonuna göre gerekli DB’ye bağlanılır.
3. Konfigurasyona göre seçili DB’ye özel SQL tümcesi oluşturulur ve veri çekme işlemi başlatılır.
4. DB’den dönen Relational veriler nesnelere dönüştürülür.
5. Session sayesinde üzerinde işlem yapılan nesneler, transaction.commit() ya da flush() komutları ile topluca kaydedilir.(Gerekli durumlarda eş zamanalılık(concurrency) kontrolleri gerçekleştirilir.)

Örnek

1. Basit bir Telefon Rehberi uygulamasını NHibernate ve Oracle kullanarak gerçekleyeceğiz. Öncelikle Oracle üzerinde DataBase’imizi şekildeki gibi modelleyelim.


2. Şimdi Mapping dosyalarını oluşturalım. Bunun için NConstruct Lite ürünü kullanacağız. İlk adımda veritabanı tipini Oracle olarak seçiyoruz. Bir sonraki adımda Connection ayarlarını ve ve Application Name’i TelRehber giriyoruz.
3. Bir sonraki adımda KISI ve TELEFON tablolarımızı seçiyoruz. Next’e bastığımızda aşağıdaki sayfa karşınıza çıkacak, buradan oluşacak nesneler ile ilgili ayarları girebilirsiniz.

4. Next’e bastığımızda oluşturulacak proje dosyası için yer gösteriyoruz. Finish diyoruz.
5. Visual Studio’yu açıyoruz. WebRehber adında yeni bir web projesi oluşturuyoruz.
6. Solution Explorer’dan projemize sağ tıklayıp Add Reference ile NHibernate.dll’i refere ediyoruz.
7. Öncelikle Projemizi NHibernate ile çalışabilecek duruma getireceğiz. Solution Explorer’da Solution üzerine sağ tuşla tıklayıp Add -> Existing Project üzerinden az NConstruct tarafından üretilen projeyi ekliyoruz. Ardından Web projemizi sağ tıklayıp Add Reference’ı tıklıyoruz. Çıkan pencereden Projects sekmesinden TelRehber.Server.Data projesini seçiyoruz.

8.Eklediğimiz projenin içindeki .hbm.xml dosyalarını Web projemizde, App_Data klasörüne sürüklüyoruz (soldaki gibi). Ardından diğer projedeki dosyaları siliyoruz.
Dikkat: NConstrcuct büyük” I”ları küçük “ı”lara dönüştürebiliyor. Bu durumda Xml ve Class dosyalarının düzeltilmesi gerekecektir. Ayrıca Number’ları Int32’ye dönüştürmesi gerekirken Double’a çevirebiliyor. “Identifier type mismatch” hatası verebilir. Xml ve Class’dan Int32 çevirilmelidir. Ayrıca Xml’lerin başındaki Generator Sequence olarak verildiyse aşağıdaki gibi düzeltilmesi gerekebilir:
<id name="Id" column="TEL_ID" type="System.Int32" unsaved-value="null">
<generator class="increment">
generator>
id>
9. NHibernate Session’ını yönetmek için Web projemize bir sınıf oluşturacağız. Session’ın tüm Request-Response Cycle’ı boyunca ulaşaılabilir, Response bittiğinde ise yok edilmesini sağlayacağız. Web projemize HibSession adlı bir sınıf koyuyoruz. Kodları aşağıdaki gibi olacaktır.


public static class HibSession
{
public static readonly NHibernate.Cfg.Configuration Configuration;
public static readonly ISessionFactory SessionFactory;

static HibSession()
{
Configuration = new NHibernate.Cfg.Configuration()
.SetDefaultAssembly(typeof(TelefonEntity).Assembly.FullName)
.SetDefaultNamespace(typeof(TelefonEntity).Namespace)
.AddDirectory(new DirectoryInfo(HostingEnvironment.MapPath("~/App_Data/")));

SessionFactory = Configuration.BuildSessionFactory();
}

public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
}
Burada Nhibernate ile ilgili konfigurasyon bilgilerinin bir kısmını da vermiş olduk. NHibernate.Cfg.Configuration() nesnesi oluştuğu anda Web.Config’e bakacaktır, ve web.config’deki bilgileri alacaktır. Diğer constructer overload’ları ile konfigurasyonu ayrı bir yerde gösterebiliriz10. Şimdi sıra geldi bu nesneyi Request-Response Cycle’ına bağlamaya. Onun için Web projemize Global.asax ekliyoruz ve şu kodları giriyoruz.
protected void Application_BeginRequest(object sender, EventArgs e)
{
ManagedWebSessionContext.Bind(HttpContext.Current, HibSession.SessionFactory.OpenSession());
}
protected void Application_EndRequest(object sender, EventArgs e)
{
ISession session = ManagedWebSessionContext.Unbind(HttpContext.Current, HibSession.SessionFactory);
if (session.Transaction.IsActive)
{
session.Transaction.Rollback();
}
if (session != null)
{
session.Close();
}
}
11. Ardından aşağıda belirtilen tagları web.config’de uygun yerlere koyunuz.
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</configSections>
Burada NHibernate’in çalışması sırasında kullanılacak ayarlar belirtiliyor. Yukarıda Web.config’e yeni bir tag ekleyeceğimizi ve bu tagın hangi sınıf ile çözümlenebileceğini belirttik(Standart bir NameValueSection). Ardından aşağıdaki NHibernate konfigurasyonunu belirtiyoruz.
<nhibernate>
<add key="hibernate.dialect" value="NHibernate.Dialect.Oracle9Dialect"/>
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="hibernate.connection.connection_string" value="Data Source=E3000;User ID=TCAKMAK;Password=*******;Unicode=True;Integrated Security=no;"/>
<add key="hibernate.bytecode.provider" value="null"/>
<add key="hibernate.current_session_context_class" value="managed_web"/>
nhibernate>
12. Artık İlk verimizi çekebiliriz. Bir sayfanın arkasına Page_Load’a aşağıdaki kodları yazıyoruz. Sonucu sayfada görebilirsiniz. Id’si 1 olan Kisi’nin ismi…
Response.Write(HibSession.GetCurrentSession().Get<KisiEntity>(1).Ad);
13. Şimdi ObjectDataSource ile daha kolay kullanabilmek için bir adet BaseService sınıf yazıyoruz. Bu sınıf içinde Session için veri ekelyip silecek metodları koyacağız; ardından Service sınıflarımızı ondan miras alacağız. Burada type-safe olarak çalışabilmek için BaseService sınıfmızı Generic Sınıf olarak belirtiyoruz.
Böylece Mesela Kisi nesnesi ile ilgili KisiService sınıfımızı BaseService<KisiEntity>’den inherit alacağız. Sadece ek iş akışlarını yeni metholar halinde ekleyeceğiz, CRUD fonksiyonları otomatik olarak kullanılabilir olacak. Burada ListAll() metodunun 2 farklı overload’u olduğuna dikkat ediniz. 2. metod sayfalama için kullanılabilecek parametreleri hazır olarak kullanabilmektedir. Ekstra bir ListAllCount metodu satır sayısını dönen bir method olarak karşımıza çıkacaktır. Bu sayede sistem kolaylıkla sayfalanabilir hale getirilebilecektir.
Kodlar aşağıda verilmiştir.
public class BaseService;
{
private ICriteria _mainCriteria;
protected ICriteria mainCriteria
{
get
{
if(_mainCriteria==null)
_mainCriteria= NSession.CreateCriteria(typeof(T));
return _mainCriteria;
}
set {
_mainCriteria = value;
}
}
ISession NSession { get { return HibSession.GetCurrentSession(); } }

public IList<T> ListAll()
{
return mainCriteria.List<T>();
}
public IList ListAll(int startRowIndex, int maximumRows)
{
return mainCriteria.SetFirstResult(startRowIndex).SetMaxResults(maximumRows).List<T>();
}
public IList ListAll(string sortParam, int startRowIndex, int maximumRows)
{
if (!string.IsNullOrEmpty(sortParam))
{
mainCriteria = mainCriteria.AddOrder(new Order(sortParam, true));
}
return mainCriteria.SetFirstResult(startRowIndex).SetMaxResults(maximumRows).List<T>();
}
public int ListAllCount()
{
return mainCriteria.SetProjection(Projections.RowCount()).UniqueResult<int>();
}

public void Insert(T item)
{
NSession.Save(item);
NSession.Flush();
}
public virtual void Update(T item)
{
NSession.SaveOrUpdate(item);
NSession.Flush();
}
public void Delete(T item)
{
NSession.Delete(item);
NSession.Flush();
}
}
14. Artık tek yapmamız gereken KisiService’i yazmak
public class KisiService : BaseService<KisiEntity> {}
15. Web Form’uma gidiyorum; bir ObjectDataSource sürüklüyorum. İsmini ODKisi olarak değiştiriyorum.

16. KisiService sınıfımı seçiyorum.

17. Select Update Insert Delete metodlarını tek tek aşağıdaki gibi seçiyoruz.


18. Finish diyorum. Bir gridview sürüklüyorum. Datasource olarak ODKisi’yi veriyorum.



19. GridView Tasks’den yandaki fotğrafda gözüken Paging, Sorting, Editing, Deleting şeçenekleri tıklıyorum. Not: ObjectDataSource ve GridView birlikte kulanılırken GridView’ın bir eksikliği nedeniyle Delete işleminde hata alabilirsiniz. Bu durumda yapılması gereken şey; GridView’ın DataKeyNames property’sine KisiID’yi girmenidir



20. Sayfalamayı Object DataSource‘da da aktif hale getirmek için aşağıdaki fotoğrafda da görüldüğü gibi EnablePaging Özelliğini True yapıyoruz. SelectCountMethod özelliğine de ListAllCount yazıyoruz.

21. Kisi’ler üzerinde her türlü CRUD fonksiyonun gerçekleştirebileceğimiz bir GridVİew’a sahip olduk; Insert işlemlerini gerçekleştirebileceğimiz de bir DetailsView koyalım. DataSource olarak yine ODKisiler’i seçelilm. Enable Inserting tıklayıp Properties penceresinden de DefaultMode’u Insert durumuna getiriyoruz.

22. Sonuçta ekran görüntümüz aşağıdaki gibi olacaktır.

Kullanılan Mimarinin Artıları


    Sadece tek satır kod ile (public class KisiService:BaseService<KisiEntity> { }) Sayfalama, Sıralama, Ekleme, Silme, Değiştirme işlemleri çok hızlı bir şekilde gerçekleştirilebildi. İsteğimize özel sorgular Service sınıfının içine koyularak kolay bir katmanlama söz konusu olabilir.

Kullanılan Mimarinin Eksiklikleri

    ObjectDatasource nesne kullanımında nesneler arasındaki ilişkileri ifade etmede bazı sıkıntıları bulunuyor. Özellikle Asp.Net’in DataBinding yapısı Eval komutu ile ilişkili nesneleri gösterebiliyorken Bind yapısının kullanımına izin vermiyor. Ancak bu problemler NhibernateDataSource adlı open source projede ortadan kaldırılmış. Belirtilen alanları sanki tek nesneymiş gibi göstererek problemi çözüme kavuşturuyor. Filtreleme, arama gibi ekranlar için neredeyse hiç enerji harcamk zorunda kalınmıyor. ICriteria yapısı güçlü ancak çoğu zaman string tabanlı çalışmak zorunda kalıyoruz.


NhibernateDataSource Eksiklikleri

    Her open source projede olduğu gibi wizard yok, GridView direk Datasource’a bağlandığında GridView kolonlarını otomatik oluşturmuyor. GridView’ın “AutoGenerateColoumns” property’sini True yapmamız gerekiyor ya da elle kolon isimlerini vermemiz gerekiyor.


Çözüm

    Nikhil Kotari(Writing Asp.Net Server Controls kitabının yazarı), kendi DataSource’umuzu yazmak konusunda MSDN’de yazdığı 5 tane makale izlenerek kendimize özel DataSource yazılabilir. Böylece NhibernateDataSource’un küçük eksiklikleri de kapatılabilir… LINQ NHibernate üzerine uygulanırsa string tabanlı çalışılmak zorunda kalınmaz.Bunun için de Ayende Rahien’in hazırladığı LINQ for NHibernate(açılan sayfada ilk sırada) open source projesi temel alınabilir. Böylece sadece Ar-Ge ekibi Framework’u geliştiriken ICretieria API’sini kullanır, Yazılım Geliştiriceiler ise gerekli olan yerlerde(CRUD fonksiyonları hazır geleceği için tekrar kod yazmak zorunda kalınmayacak) LINQ ile çalışabilecektir.

Sonuç

    Bir tür template ile; DB’si hazır olan bir proje XML configurasyonları, BaseDAO sınıfları ve onlardan inherit olan Service’lerin tanımları yapıldığında, yazılımcıya düşen iş ciddi derecede azalacaktır. Basit DB işlemleri birkaç tıklama ile, kompleks DB işlemleri ise çok hızlı ve çok kolay şekilde gerçeklenebilecektir. Belirtilen yöntemler uygulandığında yazılım geliştirme süreci çok hızlı olabilecektir

4 yorum:

  1. Oooo Hocam hoşgeldiniz :) Sahalara tam dönüş yaptık diyebilir miyiz ?

    YanıtlaSil
  2. Tahir güzel bir makale olmuş eline sağlık.

    Mustafa YUCADAĞ

    YanıtlaSil
  3. Tahir süper bi makale olmuş. Eline sağlık.
    (Oku oku bitmedi, kimbilir kaç günde yazdın :))

    YanıtlaSil