Entity Framework etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Entity Framework etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

16 Şubat 2011 Çarşamba

Entity Framework Code Fisrt Veri Doğrulama

Bir önce ki yazıda Veri Açıklaması/DataAnnotations isim uzayını inceledik. Veri açıklamaları ile veri moleliniz üzerinde property seviyesinde doğrulama mantığımızı çalıştırabiliyoruz. Veri modeli sınıflarınıza .NET 4 tarafından tamamen desteklenen [Range], [RegulerExpress] gibi doğrulama nitelikleri ekleyerek veri tabanı işlemleri yapmadan önce gerçekten kolay bir şekilde doğrulama kontrolüne zorlayabiliyoruz.

Property seviyesindeki doğrulama çok işe yaramasına rağmen tüm veri doğrulama ihtiyaclarmızı karşılamamaktadır. Veri modeli sınıfımızdaki bir çok property değerinin birlikte kontrol edileceği sınıf seviyesinde kontrol gerekmektedir.

System.ComponentModel.DataAnnotation isin uzayında bulunan IValidateableObject arayüzü ile entity nesnelerinize sınıf seviyesinde doğrulama ekleyebilirsiniz.

Bu serinin daha önce ki yazılarnda ki gibi bir adres defteri uygulaması üzerinden sınıf seviyesinde doğrulama nasıl yapılır inceleyelim. Aşağıda ki örnekte kişi tanımlarını taşıyan Person entity sınıfımız yer almaktadır. Yapmak isteğimiz adres defterinde ki her erkeğin şirket bilgisinin her bayanında doğum günü bilgisinin boş bırakılmamasını sağlamak. Bu kuralı işletebilmek için aynı anda en az iki property değirini kontrol etmemiz gerekmektedir.

[cs]

public  partial  class  Person  : IValidatableObject 
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
     public  IEnumerableValidate(ValidationContext validationContext)
     {
         if  (IsFemale  && BirthDay  ==  null )
             yield  return   new  
                 ValidationResult (
                     "Bayanların doğum günü boş bırakılamaz" ,
                     new [] { "BirthDay"  });
         if  (!IsFemale  && string .IsNullOrEmpty (Company ))
             yield  return  new  
                 ValidationResult (
                     "Erkeklerin şirket bilgisi boş bırakılamaz" , 
                     new [] { "Company"  });
     }
 }

Yukarıda ki kod bloğunda erkeğin adres bilgisi ve bayanın doğum günü bilgisi kontrol edilmektedir. IValidateableObject.Validate() metodu bir çok property değerini kontrol etmekte ve bir çok doğrulama hatası dönebilmektedir. Doğrulama hatalarının birinci parametresi hata mesajıdır. İkinci parametre ise hataya sebeb olan property isimleri dizisidir.

Otomatik Doğrulamaya Zorlamak

Entity Framework Code First IValidateableObject arayüzünü uygulayan entity neslerini kayıt ederken Validate metodunu otomatik olarak çağırmaktadır. Validate metodunu çağırmak için her hangi bir kod yazmanıza gerek yoktur. DbContext.SaveChanges() metodunu çağırdığınızda IValidateableObject.Validate EF Code First tarafından çağırılacak ve eğer hata oluşursa tüm transaction otomatik olarak geri alınacaktır.

[cs]

var  person = new  Person 
         {
             FullName  = "Ali Veli" ,
             IsFemale  = false ,
             BirthDay  = new  DateTime (2000, 1, 1)
         };
 var  dbContext = new  AdressBook ();
 dbContext.Persons .Add (person);
 // Ali Veli kişi kaydını IvalidateableObject.Validate 
// metodunda ki kurala uymadığı için 
// aşağıda ki satırda exception throw edecektir
 dbContext.SaveChanges ();

Hatanın throw edilmesi her zaman istenmeyen bir durumdur. Çünkü throw edilen hatanın arayüze yakın bir yerde catch edilmesi gerekmektedir. Bu sebebden entity nesnenizi kontrol etmek ve eğer veri iş kurallarına uygunsa veri tabanı işlemleri yapmak isteriz. SaveChanges() metodunu çağırmadan önce GetValidationErrors() metodu ile doğrulama hatalarını throw edilmeden elde ederiz.

[cs]

var  person = new  Person 
 {
     FullName  = "Ali Veli" ,
     IsFemale  = false ,
     BirthDay  = new  DateTime (2000, 1, 1)
 };
 var  dbContext = new  AdressBook ();
 dbContext.Persons .Add (person);
 // tüm hataları listele 
 var  errors = dbContext.GetValidationErrors ();
 if (errors.Count () > 0 )
     // hataları ekranda göster 
     errors.ToList ().ForEach (
         c=>c.ValidationErrors .ToList ().ForEach (
             l=>ModelState .AddModelError (l.PropertyName ,l.ErrorMessage )));  
 else 
     dbContext.SaveChanges ();

Arayüz Entegrasyonu

Bir önce ki yazıda DataAnnotation isim uzayının .NET 4 ile getiştirilen tüm platformlar tarafından desteklendiğine değinmiştik. MVC 3 ile entity sınıfını hazırladığımız adres defteri uygulamamızı geliştirelim ve sınıf seviyesinde doğrulama işleminin önyüzde nasıl bir etki oluşturduğunu görelim. Öncelikle bir PersonController sınıfına göz atalım.

[cs]

public  ActionResult  Create ()
 {
     return  View ();
 }
 [HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Yukarıda standart MVC Controller sınıfı kodlaması bulunmaktadır. İlk Create motodu ile arayüz hazırlanmakta ve ikinci Create(person) metodu ile kullanıcının girdiği Person verisi veri tabanına kayıd edilmektedir. Create(person ) metodu öncelikle gelen verinin doğrulamaları geçmesini eklemektedir. Eğer doğrulamaları geçti ise veri tabanına kayıt etmektedir. Veri doğrulama için ekstra hiçbir kodlama bulunmamaktadır. Veri doğrulaması DataAnnotation isim uzayına duyarlı MVC tarafından otomatik olarak yapılmaktadır. Birde önyüzü inceleyelim. Aşağıdaki Create() metodunun döndüğü ön yüz görülmektedir. Burada da hiçbir fazladan kodlama yok.


  @model   RoC.CodeFirst.Models.Person
  @{
     ViewBag.Title = "ViewPage1" ;
  }
 <h2> ViewPage1</h2>
 <script  src="  @Url.Content("~/Scripts/jquery.validate.min.js")"  type="text/javascript"></script>
 <script  src="  @Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"  type="text/javascript"></script>
  @   using  (Html.BeginForm()) {
      @  Html.ValidationSummary(true )
     <fieldset>
         <legend> Person</legend>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.FullName)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.FullName)
              @  Html.ValidationMessageFor(model => model.FullName)
         </div>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.Company)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.Company)
              @  Html.ValidationMessageFor(model => model.Company)
         </div>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.BirthDay)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.BirthDay)
              @  Html.ValidationMessageFor(model => model.BirthDay)
         </div>
         <p>
             <input  type="submit"  value="Create"  />
         </p>
     </fieldset>
 }
 <div>
      @  Html.ActionLink("Back to List" , "Index" )
 </div>

Yukarıda ki Controller ve View entegrasyonu ile fazladan hiçbir doğrulama mantığı kodlamadık. Doğrulama mantığını tamamen veri modeli üzerine taşıdık. İş mantığının yazıldığı Controller sınıfı sadece ModelState.IsValid() fonksiyonu ile doğrulama sonucunu kontrol etti.

Eğer IValidateableObject.Validate() metodundan bir doğrulama hatası dönerse ModelState.IsValid otomatik false değerini alacaktır. Controller’ın ModelState.IsValid false değerini alınca View geri döndürdüğünü görülmektedir. Arayüz tekrar gösterilirken IValidateable.Validation() metodunun sonucları ValidationMessageFor metodu ile arayüzde gösterilecektir Sonuç olarak hatalı bir işlemi kayıd etmeye çalıştığımızda aşağıdaki ekran görüntüsüne ulaşırız.

Veri açıklaması/DataAnnotation ve sınıf seviyesinden doğrulama/IValidateableObject ile Entity Framework Code First bir çok uygulamanın doğrulama ihtiyaçlarına cevap vermektedir.

Sonuç

Entity Framework Code First ile birlikte doğrulama ve iş kurallarının veri modeli üzerinde kalması sağlanmıştır. Hedeflenen kendini tekrar etmeyen kodlama (DRY – Dont Repeat Yourself) yapabilmektir. Tüm iş kodları bir yerde bulunsun, basit okunabilir kodlamalar olsun ve asla iş kodları farklı yerlerde tekrar edilmesin istenmektedir. En çok tekrar edilen kodlama ise doğrulama kodlamasıdır. Hem istemci hem sunucu tarafında aynı veri kontrolleri yapılmaktadır. EF Code First (ve MVC 3) ile sunulan veri açıklaması ve sınıf seviyesi doğrulama çözümleri ile kendini tekrar eden doğrulama kodlamalarına çözüm getirmektedir.

14 Şubat 2011 Pazartesi

Veri Açıklama - Data Annotations

“System.ComponentModel.DataAnnotations” isim uzayı NET 3.5 SP1 ile gelen Asp.Net Dynamic Data projesi ile hayatımıza girdi. Hayatımıza girdiği andan itibaren geliştirilen tüm platformlar bu isim uzayına duyarlı geliştirildi. Asp.Net Dynamic Data, Wcf Ria, Silverlight ve şimdide MVC 3 bu isim uzayı ile entegre calışmaktadır. Peki veri açıklması/Data Annotations isim uzayı nedir nasıl kullanılır?

Veri Modelini Açıklama

“System.ComponentModel.DataAnnotations” isim uzayı [Required] [Display] gibi semantik nitelikleri kullanarak UI üzerinde kullanılabilecek veri açıklamaları sunmaktadır. DataAnnotations isim uzayı ile birlikte sunulan çözüm veri modeli açıklamaları ile her uygulamada yapılan doğrulama, lokalizasyon gibi ön yüz işlemlerinin otomatik hale getirilmesidir.

Nasıl Çalışır ?

ASP.Net web form, Silverlight (wcf ria) ve Mvc platformları DataAnnotations isim uzayını desteklemektedir. Ben örneklemek için MVC 3 ve Entity Framework Code First uygulaması kullanacağım. Burada anlatılan Entity Framework Code First adress defteri uygulamamıza veri modeli açıklamaları ekleyerek nasıl çalıştığını inceliyelim.

Aşağıda adres defteri uygulaması için gerekli veri modeli bulunmaktadır.

[cs]

public  partial  class  Person 
 {
     public int  PersonId  { get ; set ; }
     public string  FullName  { get ; set ; }
     public string  Company  { get ; set ; }
     public DateTime  BirthDay  { get ; set ; }
     public virtual ListAdresses{ get; set;}
 }

Yapmak istediğimiz kişi verisi girilirken Name değeri boş bırakılmasın veya yirmi karakterden uzun değerler girilmesin. MVC uygulamamızda bu istediğimizi aşağıdaki şekilde yapabiliriz.

[cs]

[HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if (string .IsNullOrEmpty (person.FullName ) )
         ModelState.AddModelError("FullName","Ad değeri boş bırakılamaz" );
     if  (person.FullName  != null  && person.FullName .Length  > 20)
         ModelState.AddModelError("FullName","20 karakterden uzun ad girilemez");
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Bu kod bloğunda girilen verinin veri yapımıza uygunluğunu test ettik. Fakat sadece bir tek property için bile bir çok if yazmak zorundayız. Karmaşık veri yapalarındaki doğrulama işlemi ciddi bir sorun oluşturacaktır. Bir diğer doğrulama seceneğimizde veri modeli açıklaması oluşturmaktır.

[cs]

[MetadataType (typeof (PersonMetadata ))]
 public  partial  class  Person 
 {
 public  class  PersonMetadata 
 {
     [Key , 
     Display (AutoGenerateField  = false )]
     public  int  PersonId  { get ; set ; }
     [Required (ErrorMessage  = "Ad değeri boş bırakılamaz" ),
     Display (Name  = "Ad" ),
     StringLength (20,ErrorMessage  = "20 karakterden uzun ad girilemez" )]
     public  string  FullName  { get ; set ; }
     [Display (Name  = "Şirket" ),
     StringLength(20,ErrorMessage="20 karakterden uzun şirket ismi girilemez")]
     public  string  Company  { get ; set ; }
     [Display (Name  = "Doğum Tarihi" ), 
     Required (ErrorMessage  = "Doğum tarihi boş bırakılamaz" ),
     DataType (DataType . Date  )]
     public  DateTime  BirthDay  { get ; set ; }
 }
 }

Kişileri saklayan bu sınıfa veri modeli açıklamaları ekledik. Doğrudan sınıflarda bulunan public property’lerede açıklama niteliği ekleyebilirsiniz. Ben daha okunabilir olması için Metadata sınıfı ile birlikte veri modeli açıklama niteliklerini kullandım.Yukardaki kod bloğunda Name özelliğinin ekranda “Ad” olarak görüleceğini değerinin boş bırakılamıyacağını ve yirmi karakterden uzun veri girilemeyeceğiniz söylemektedir. Veri doğrulamalasını belirtilen açıklamalara göre MVC bizim için yapacağından Create metodumuz aşağıdaki şekilde olacaktır.

[cs]

[HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Yukarıda ki kod bloğunda hiç bir veri doğrulaması yapılmıyor. Çünkü ModelBinder sınıfı verdiğimiz veri açıklama nitelikleri ile veri doğrulamasını yapmakta ve sonuçları ModelState verisine yüklemektedir. Sunucu tarafında yapılan kontroller gibi MVC istemci taraftada veriyi doğrulamaktadır. Şimdi MVC projemizde kişi ekleme sayfası oluşturalım.

  @   using  (Html.BeginForm()) {
  @  Html.ValidationSummary(true )
 <fieldset>
     <legend> Person</legend>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.FullName)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.FullName)
          @  Html.ValidationMessageFor(model => model.FullName)
     </div>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.Company)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.Company)
          @  Html.ValidationMessageFor(model => model.Company)
     </div>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.BirthDay)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.BirthDay)
          @  Html.ValidationMessageFor(model => model.BirthDay)
     </div>
     <p>
         <input  type="submit"  value="Create"  />
     </p>
 </fieldset>
 }

Yukarıda ki kodda standart bir MVC Razor sayfası görülmektedir ve tüm kodu MVC oluşturdu. LabelFor, EditorFor ve ValidationFor metotları bizim veri modeli açıklalarımızı dikkate alacak ve istediğimiz önyüzü oluşturacaktır.

Görüldüğü üzere MVC veri modeli açıklamaları dikkate aldı ve gerekli şekilde önyüzü ve istemci tarafı doğrulama metotlarını oluşturdu. İstemci tarafında ki javascript fonksiyonlarının ve sunucu tarafında veri kontrollerinin otomatik olarak oluştuğuna özellikle dikkat ediniz. Biz ekstra hiç bir kodlamada bulunmadık.

Daha Fazlası

Sistem içinde bir çok genişleme noktası mevcuttur. Kendi doğrulama/validator sınıflarınızı yazabilirsiniz. Data tiplerine özel önyüz kontrolleri oluşturablirsiniz. MVC örneğimizde veriyi önyüz ve sunucu kodları arasında taşıyan binder sınıfı mevcuttur. Kendinize ait DataAnnotations niteliklerinize özel ModelBinder örneği oluşturabilirsiniz.

Sonuç

Veri modelini doğrulama yaklaşımı aslında bir ilgileri ayırma/(Speration of concers) yöntemidir. En bilinen ilgileri ayırma yöntemi Html ve css kodlarının ayrılmasıdır. DataAnnotations ile veri modeli ve veri modelini ilgilendiren veri doğrulama, görüntüleme gibi özellikler veri modeli sınıfı üzerinde turuluyor. Veri ilgilendirmeyen önyüz kodları ayrı bir yerde tutuluyor. Sunucu tarafında ki iş mantığı kodları ayrı bir alanda tutuluyor.

Veri modeli açıklamasının tüm platformlarda aynı şekilde kodlanması ve kolay anlaşılabilirlik ile birlikte sistemin bir çok genişleme noktası bulunması sistemi kullanılabilir olmasını sağlamaktadır. Ciddi bir kodlama yüküne çözüm getirmesi tüm platformlarda desteklenmesi DataAnnotations isim uzayının hayatımızda çok fazla yer alacağını göstermektedir.

7 Şubat 2011 Pazartesi

Entity Framework Code First- Veri Tabanı Eşleştirme

Code First kod merkezli geliştirme ortamı sağlamaktadır. Her hangi bir tool veya XML eşleştirme dosyasına gerek kalmaksınız geliştirme yapabilirsiniz. Veri modeli nesnelerinizi her hangi bir sınıftan türetmeden basit POCO sınıfları olarak yazabilirsiniz. Konfigurasyon yerine isimlendirme özelliklerini kullanarak POCO nesnelerinizi veri tabanı nesneleriniz ile eşleştirebilirsiniz. Serinin ilk yazısında EF Code First bu özelliklerin nasıl kullanıldığını inceledik.

Varsayılan entity-veri tabanı eşleşmesi nasıl değiştirilir? EF Code First ile varsayılan olarak gelen entity ve veri tabanı nesneleri arasında ki eşleştirme nasıl değiştirilir?

Seri boyunca takip ettiğimiz adres defteri uygulamamıza devam edip yukarıda ki sorularmızın cevabını arıyalım. Standart .NET tipleri olan POCO nesnelerimiz hatırlayalım.

[cs]

public  partial  class  Person  
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }
 public  partial  class  Adress 
 {
     public  int  AdressId  { get ; set ; }
     public  string  AdressType  { get ; set ; }
     public  string  FullAdress  { get ; set ; }
     public  int  PersonId  { get ; set ; }
     public  virtual  Person  Person  { get ; set ; }
 }

Bu POCO nesnelerimiz ile veri tabanı eşleşmesini sağlayan DbContext sınıfmızıda hatırlayalım. AddressBook DbContext sınıfından türetilmişti ve Persons ve Adresses property’lerine sahipti.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
 }


Veri tabanı eşleşmeleri için Code First yaklaşımını kullandık. Bunun manası AdressBook sınıfında yer alan Persons ve Addresses bizim veri tabanımızda aynı isimde ki tablolarla eşleşecekdir. Her bir Persons ve Adresses sınıflarıdandaki her bir property’de Persons ve Adresses tablolarında ki kolonlar ile eşleşecekdir.

Ekstra her hangibir konfigurasyon veya kodlama gerekmeden veri tabanı tablolarımız oluşmaktadır. Veri tabanımızda oluşan veri yapımız aşağıdaki gibidir.

Örnek uygulamada SQL CE kullandım. Yukarıdaki veri yapısında görüldüğü gibi Entity Framework Code First varsayılan olarak entity nesnemize birebir benzeyen bir tablo yapısı oluştudu.

Veri Tabanı Eşleştirme

Entity Framework Code First ile gelen veri tabanı eşleştirmesini DbContext.OnModelCreating metodunu override ederek değiştirebilirsiniz.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
     protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
     {
         // entity - tablo eşleşmesini değiştir 
     }
 }

OnModelCreating(modelBuilder) metodu veri tabanınız ilk defa oluşturulurken çağırılacakdır. OnModelCreating metotu ile veri tabanı nesnelerinizin nasıl oluşturulması gerektiğini söyleyebilirsiniz. Adım adım olabilecek senaryoları inceleyelim.

Tablo İsmini Değiştirme

Nasıl Persons tablosunun ismini aşağıdaki gibi tblPersons yaparız?

Persons tablosunu tlbPersons yapmak için gerekli olan OnModelCreating metodu içinde Entity sınıfmıza ToTable(name) metodu ile özelleştirilmiş bir tablo adı vermektir.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
     protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
     {
         modelBuilder.Entity ().ToTable ("tblPersons" );
     }
 }

Yukarıda ki kod bloğu tablo ismi vermek için kullanılan sezgisel/fluent kuralları devre dışı bırakır ve OnModelCreating metotu ile belirtilen yeni kuralları uygulayarak tablo isimlendirmesini yapılır.

Kolon İsimlerini Değiştirme

Nasıl aşağıdakii gibi tüm kolonlara "col" ön eki ekleyebiliriz?

Tüm kolonlarımıza “col” ön eki eklemek için OnModelCreating metodu içinde entity nesnemiz için yeni bir eşleştirme/mapping eklememiz gerekmektedir.

[cs]

public  class  AdressBook  : DbContext 
 {
 public  DbSet  Persons  { get ; set ; }
 public  DbSet  Adresses  { get ; set ; }
 protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
 {
     modelBuilder.Entity ().ToTable ("tblPersons" );
     modelBuilder.Entity ().Property (c => c.PersonId ).HasColumnName ("colId" );
     modelBuilder.Entity ().Property (c => c.FullName ).HasColumnName ("colFullName" );
     modelBuilder.Entity ().Property (c => c.Company ).HasColumnName ("colCompany" );
     modelBuilder.Entity ().Property (c => c.BirthDay ).HasColumnName ("colBirthDay" );
     modelBuilder.Entity ().Property (c => c.IsFemale ).HasColumnName ("colIsFemale" );
     modelBuilder.Entity ().ToTable ("tblAdresses" );
     modelBuilder.Entity ().Property (c => c.AdressId ).HasColumnName ("colId" );
     modelBuilder.Entity ().Property (c => c.FullAdress ).HasColumnName ("colFullAdress" );
     modelBuilder.Entity ().Property (c => c.PersonId ).HasColumnName ("colRefId" );
 }
 }

Yukarıdaki kod bloğunda mevcut kurallar yerine OnModelCreating(modelBuilder) içindeki kuralların işletilmesi sağlanmıştır . Böylelikle varsayılan eşleştirme kuralları override edilmektedir. Code First ile Ansi SQL kolon yapısında desteklenen tüm özellikleri yukarıdaki gibi kullanabilirsiniz. Daha ayrıntılı özellikleri buradan inceleyebilirsiniz.

Bir Çok Entity Bir Table

En sık karşılaşılan seneryolardan biriside uygulama içinde ki entity nesnemiz ile veri tabanında ki tablo yapılarımızın bire bir örtüşmemesidir. Normalizasyon kuralları gereği aynı tabloda yer alması gereken veriler, nesne güdümlü yazılım mimarisi içinde farklı sınıflarda yer alması gerekebilmektedir. Aşağıdaki veri yapısında Person tablosuna göze atalım.

Burada IsWorkWith, IsDrunkWith, MustTakeGift bilgisi gibi alanlar Person tablosunda yer alması istiyoruz. Çünkü bu veriler Person verisine niteleyen verilerdir. Fakat bu bilgi özelleşmiş bir bilgi olduğu için farklı bir entity içinde taşınması daha doğrudur.

[cs]

public  partial   class  Familiarity 
 {
     public  bool  IsWorkWith  { get ; set ; }
     public  bool  IsDrunkWith  { get ; set ; }
     public  bool  MustTakeGift  { get ; set ; }
 }
 public  partial  class  Person  
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  Familiarity  Familiarity  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }

Yukarıda ki entity yapısı ve veri yapısını birbirine uyumlu hale getirmemiz gerekmektedir. İki yapıyı uyumlu hale getirmek için OnModelCreating() metodu içinde Person entity sınıfımızda yer alan Contact property’sinin bir başka entity sınıfı olmadığını Contact sınıfının bir ComplexType olduğunu belirtmemiz gerekmektedir.

[cs]

protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
 {
     modelBuilder.ComplexType ();
     modelBuilder.ComplexType ()
  .Property (c => c.MustTakeGift ).HasColumnName ("colMustTakeGift" );
     modelBuilder.ComplexType ()
  .Property (c => c.IsDrunkWith ).HasColumnName ("colIsDrunkWith" );
     modelBuilder.ComplexType ()
  .Property (c => c.IsWorkWith ).HasColumnName ("colIsWorkWith" );
 }

Böylelikle Contact sınıfımız bir entity sınıfı olarak değilde yer aldığı entity nesnesinin alt sınıfı olarak işlem görecektir.

Sonuç

Entity Framework Code First veri erişimini kod merkezli güzel bir yöntem ile çözmektedir. Veri erişimi mekanizmasında yer alan değiştirme noktalarından müdahale ederek veri erişim yapısını kendinize göre özelleştirebilirsiniz. Bu yazıda OnModelCreating(modelBuilder) değiştirme noktasına müdahale ederek veri tabanı yapısını değiştilmesini inceledik. Tüm işlemlerin kodlama ile basitce yapılabilmesi fazladan konfigürasyon gereketirmemesi, doğrulama özelliklerinin önyüz uygulamalar tarafından desteklenemesi gibi özellikler Entity Framework ile uygulama yazmayı kolaylaştırmaktadır.

30 Ocak 2011 Pazar

Entity Framework Code First

EF 4 ile birlikte birçok yeni özellik gelmiştir.

  • POCO: Entity sınıflarının bir üst sınıftan türetilmek zorunda değiller
  • Lazy Load: Entity örneği alt Entity özelliklerini ilk erişim anında yükleyebilirler
  • Self-Tracking: Entity örnekleri değişim durumlarını kendileri takip ediyorlar. Böylece durum bağımsız (stateless) web çağrılarında kullanılabilirler.
  • Test: EF 4 nesneleri interface’ler aracılığı ile daha kolay test edilebilir durumdalar
  • Linq: Artık EF 4 tüm linq metotlarını destekliyor

Visual Studio içinde EF 4 için birçok model tasarımcısı ve araç eklenmiştir. EF tasarımcıları “Model First” olarak adlandırılan yöntem için kullanılırlar. Model First önce veri tabanında veya EF 4 model tasarımcısında veri modelin çıkartılması daha sonra çıkartılan modele ait kodların kod şablonları (T4) ile oluşturulması yöntemidir.

Code First

Entity Framework 4 ile tasarımcı temelli Model First veya Database First yaklaşımına ek olarak “Code First” olarak adlandırılan kod merkezli yeni bir yaklaşım ortaya konmuştur. Code First ile her hangi bir tasarımcıya, xml eşleştirme dosyasına veya bir Entity Base Class’a ihtiyacınız yoktur. Model ile Entity sınıflarınız arasında ki eşleştirmeyi isimlendirme kuralları ile yapabilirsiniz ve bu eşleştirmeyi tamamen kodlama ile özelleştirebilirsiniz.

Uygulama Geliştirme

Öncelikle bir uygulama oluşturalım. Daha sonra Nuget ile projemize EFCodeFirst ekleyelim.

Bir veri tabanı ile başlamamıza gerek yok. Bunun yerine veri yapımızı (Domain Model ) taşıyan standart NET sınıfları yazarak işe başlayacağız.

Model Sınıflarını Oluşturma

Küçük bir adres defteri uygulaması yazıyoruz ve bizim veri yapımız gerçekten küçük bir yapıdır. Veri modelimizde sadece Kişiler ve Kişi Adresleri var. Bu basit iki sınıfı oluşturalım.

[cs]

public  class  Person 
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime  BirthDay  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }
 public  class  Adress 
 {
     public  int  AdressId  { get ; set ; }
     public  string  AdressType  { get ; set ; }
     public  string  FullAdress  { get ; set ; }
     public  int  PersonId  { get ; set ; }
     public  virtual  Person  Person  { get ; set ; }
 }

Yukarıda ki Person ve Adress model sınıfları POCO sınıflardır. Her hangi bir üst sınıftan veya interface’ den türetmek zorunda değiliz. Her hangi bir veri tabanı bağlantısı da belirtmek zorunda değiliz. Veri modeline uyan herhangi bir veri tabanına bağlanabilirler.

Context Sınıfını Oluşturma

POCO sınıflarımızı oluşturduk. Veri tabanımız ile POCO sınıflarımızı eşleştirme işlemini yapacak Context sınıfımızı yazabiliriz.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
 }

AdressBook eşleştirme sınıfının DbContext sınıfından türetilmelidir. AddressBook sınıfı iki public özelliği Persons ve Adresses özellikleride DbSet sınıfının uygulamalarıdır. DbContext ve DbSet sınıfları EF Code First kütüphanesinde yer almaktadır.

İhtiyacımız olan tüm kodlamayı yaptık. Veri tabanı ile eşleştirme için başka bir işleme ihtiyacımız yok. Her hangi bir veri tabanı şema eşleştirme dosyası veya eşleştirme aracı veya her hangi bir tasarımcı kullanmayacağız. Veri tabanı üzerinde gerekli tüm işlemleri bu üç sınıf yapacak.

Kural Tabanlı Eşleştirme

Her hangi bir eşleştirme verisi olmadan bu üç sınıf nasıl veri tabanında ki nesneler ile eşleşecekler?

EF Code First “yapılandırma yerine kurallar” yaklaşımı ile eşleştirme yapmaktadır. Tabi ki bu varsayılan kuralları değiştirebilirsiniz.

Nedir EF Code First kuralları?

EF Code First “sadece çalış” mantığı ile mümkün olduğu adar az veri ile çalışabilecek şekilde tasarlanmıştır. Mesela Adres defteri örneğimizde Persons ve Adress olarak iki tablo oluşturacaktır. Bu tabloların kolonları da sınıfların property’lerinden oluşacaktır. Primary key olarak da tablo adı ile aynı sonu Id ile biten property kullanılacaktır. Foreing Key olarakda sınıf içinde ki diğer nesne referansları kullanılacaktır.

Veri Tabanı

Kodlarımız yazdık. Peki ya veri tabanı? Kodları yazarken hiçbir veri tabanına ihtiyacımız yok. Fakat çalışma anında veri tabanı gerekmektedir. İki şekilde veri tabanı oluşturabiliriz. İlki kendimiz gider uygun veri tabanını oluştururuz. İkincisi EF Code First bizim yerimize otomatik olarak tüm veri tabanını oluşturur. Adres defteri uygulamamız için ikinci yolu tercih ediyorum. EF Code First varsayılan olarak DbContext sınıfı ile aynı isimde bir bağlantı adı arayacaktır. Bizim DbConext sınıfımızın adı AdressBook olduğu için aynı isimde bir bağlantı adı kurmalıyız.

   <connectionStrings>
     <add name= "AdressBook "
          connectionString= "Data Source=|DataDirectory|AdressBookDb.sdf "
          providerName= "System.Data.SqlServerCe.4.0 " />
   </connectionStrings>

Bu örnekte ben veri tabanı olarak SQL CE veri tabanında faydalandım.

Model Nasıl Kullanılır?

Tüm ihtiyacımızı karşılayan üç sınıfı yazdık. Linq ile istediğimiz tüm sorguları çalıştırabiliriz.

Tüm kişi listesini getiren bir linq expression yazalım:

[cs]

var  adressBook = new  AdressBook ();
 var  list = from  p in  adressBook.Persons 
                 orderby  p.FullName 
                 select  p;

İki tablo arasında ki ilişkiden de faydalanabiliriz:

[cs]

var  adressBook = new  AdressBook ();
 var  list = from  p in  adressBook.Persons 
             where  p.Adresses .Count  > 0
             orderby  p.FullName 
             select  p;

Yukarıda ki kodda sadece adres verisi olan kişiler listelenmiştir. Primary Key özelliğinden de faydalanabiliriz:

[cs]

var  adressBook = new  AdressBook ();
 var  person = adressBook.Persons .Single (c => c.PersonId  == id);

CRUD işlemleri olmadan olmaz. Yeni bir kişi eklemek için yeni bir Person nesnesi oluşturup gerekli property’leri doldurmalıyız. Daha sonra DbContext üzerinde ki ilgili DbSet’e yeni kişi nesnemizi eklemeliyiz. Son olarak da yaptığımız değişikliği veri tabanına yansıtmamız gerekmektedir.

[cs]

var  person = new  Person 
 {
     FullName  = "Ali Veli" , 
     Company  = "ana okulu" , 
     BirthDay  = DateTime .Today 
 };
 var  adressBook = new  AdressBook ();
 adressBook.Persons .Add (person);
 adressBook.SaveChanges ();

Kayıt güncellemek için önce güncelleyeceğimiz kaydı buluyoruz. Sonra gerekli değişikliği yapıyoruz. En son yapılan değişikliği veri tabanına yansıtıyoruz.

[cs]

var  adressBook = new  AdressBook ();
 var  person = adressBook.Persons .Find (id);
 person.Company  = "FOG" ;
 person.BirthDay  = DateTime .Today .AddYears (20);
 person.FullName  = "Deli Veli" ;
 adressBook.SaveChanges ();

Sonuç

ORM araçları iş uygulaması geliştirmenin en önemli parçasıdır. EF 4 Code First veri ile kod temelli çalışılabilecek bir yol sunmaktadır. EF Code First çok güçlü bir veri işleme yöntemidir. Bu yazıda EF Code First varsayılan kuralları ile ele alınmıştır. Burada değinilmemiş birçok özellik var. System.ComponentModel.DataAnnotations isim uzayında var olan nitelikler ile entity nesnelerinize doğrulama özellikleri ekleyebiliriz. IValidatableObject arayüzü ile sınıf seviyesinde doğrulama metotları ekleyebiliriz. DbContext sınıfının OnModelCreating metodu ile entity ve veri nesnelerini eşleştirmelerini özelleştirebilirsiniz. (Böylece mevcut veri tabanları ile EF Code First yaklaşımını kullanabilirsiniz. ) DatabaseIntialize sınıfları ile veri tabanı oluşunca eklenecek varsayılan kayıtları girebilirsiniz vs.

EF Code First tümleşik bir veri işleme yöntemi sunmaktadır. EF Code First ile kod geliştiriciler daha aktif kullanılabilen bir veri işleme ortamına sahip oldular.


28 Ocak 2011 Cuma

SQL CE

SQL CE kolay veritabanı depolaması sağlayan ücretsiz, gömülü, veritabanı motorudur. SQL CE kullanmak için her hangi bir veri tabanı kurulumu gerektirmez. Sadece gerekli dll’ler bin dizinine kopyalanır ve uygulamanız veri tabanını motorunu kullanır. SQL CE çalışma döngüsü uygulamanın veri tabanına ilk erişim ile başlar ve uygulama kapatılınca uygulama ile birlikte kapatılır. Uygulama ile birlikte kapanan SQL CE veri tabanı otomatik olarak bellekten temizlenir.

SQL CE veri tabanı verileri dosya içinde saklamaktadır. SQL CE veri tabanını sadece dosyayı kopyala-yapıştır yaparak taşıyabilirsiniz. Dosyada verileri saklaması rağmen ASP.NET gibi çok kanallı kullanıma da uygundur. Her hangi bir çökme veya kilitlenme riski yoktur. Tüm uygulamalarda rahatlıkla kullanılabilinir.

Nasıl Kullanılır?

SQL CE kullanmak için geliştiriciler ekstra hiçbir bilgiye ihtiyaçları yoktur. Mevcut tüm .NET veri erişim metotları ile SQL CE erişimi sağlanabilmektedir. Kod geliştiriciler için en uygun veri tabanıdır. SQL CE veri tabanını kullanmak için önce Visual Studio SP 1 (beta)yı yüklemeniz gerekmektedir. Daha sonra SQL CE Tools For Visual Studio yüklemeniz gerekmektedir.

Örnek Uygulama

Örnek bir Asp.Net adres defteri uygulaması yapalım. Öncelikle boş bir Asp.Net uygulaması açalım.

Uygulamaya SQL CE Veri Tabanı Ekleme

Açtığımız boş uygulamanın App_Data dizinine SQL CE veri tabanını aşağıda ki gibi ekleyelim.

Veri tabanını ekledikten sonra projenin görüntüsü aşağıdaki gibi olacaktır.

Proje referanslarına System.Data.SqlServerCe kütüphanesi ve App_Data dizinine Sql Ce veri dosyasının eklendiğini görüyoruz. Veri tabanı ile çalışmak için gereken kütüphaneyi ve verilerin saklanacağı veri dosyasını projemize eklemiş olduk.

Eklediğimiz Sql Ce dosyasına çift tık ile içini açalım. Şimdi yeni bir tablo ekleyebiliriz.

Eklediğimiz tablolarımızın veri modelini oluşturalım ve daha sonra bu tablolara veri girelim. Örnek adres defteri uygulamamız için People tablosu oluşturdum ve tabloya deneme kayıtlarını girdim.

SQL CE Veri Yapısını Alma

Tüm SQL CE işlemlerimizi tamamladık. Artık uygulama tarafında kullanabiliriz. SQL CE veri tabanına Entity Framework ile bağlanmak için Entity Data Model ekleyelim.

Açılan ekranda “Generate from Database” seçeneğini seçip ilerleyelim. Sonra ki ekranda eklediğimiz AdressBook.sdf dosyasının seçili geldiğini göreceksiniz.

Sırada ki ekranda ekran da SQL CE veri tabanımızda ki tabloların geldiğini görüyoruz. Burada iki tabloyu da seçip sihirbazı tamamlayalım. Oluşan Entity Modeli aşağıda ki gibi olacaktır.

SQL CE ile Çalışmak

Veri tabanında oluşturduğumuz modeli uygulamamıza taşımış olduk. Şimdi veriyi ekranda göstermek gerekiyor. Uygulamanın ana sayfasına bir tane grid kontrolü ekleyelim. Grid kontrolünün DataSourceId özelliğinden “<New Data Source>” seçeneğini seçelim. Karşınıza aşağıda ki veri kaynağı ayarlama ekranı gelecektir.

Ben örnek olarak Entity Framework alt yapısını kullanmak istediğim için Entity seçeneğini işaretledim. Sonra ki sayfada projede ki veri kaynaklarının listesi gelmektedir. Oluşturduğumuz entity data modeli listeden seçelim.

Sonra ki sayfada gride hangi tabloyu bağlamak istediğimizi sormaktadır. People tablosunu seçelim.

Artık uygulamayı çalıştırıp önyüzden kayıtları değiştirebiliriz.

Sonuç

SQL CE kolayca veritabanı depolamasını etkinleştirmek için kullanabileceğiniz ücretsiz, gömülü, veritabanı motoru sağlar.

SQL CE ile yapılan çalışmaları isterseniz kolayca uygulamanın herhangi bir kodu değiştirmek zorunda kalmadan SQL Server veya SQL Azure üzerine taşıyabilirsiniz. Yapmanız gereken sadece uygulama yapılandırma dosyasında <connectionString> değerini değiştirmektir. Böylece uygulama geliştirmek için gerekli küçük gömülü veritabanı çözümü esnekliği sağlanmış olmaktadır.

31 Mart 2007 Cumartesi

Generic Oracle DataAccess Layer + Typed DataSet

Data Access Layer için manüel kodlama yapar mısınız? Yapıyorsanız hep aynı kodları yazmaktan sıkılmadınız  mı ? :) (Bulduğunuz çözüm Code Generator yazmak mıdır? Dilin nimetlerinden yararlanın derim.) SQL Server ortamında Data Access Layer için en rahat çözüm tabii ki Typed DataSet yani XSD dosyalarıdır. Typed DataSet'ler üzerinde TypedDataSetGenerator ile çok kolay bir şekilde veri tabanı üzerindeki Store Procedure, Table, View gibi nesnelere ulaşıp gerekli olan kod bloğunu üretebilirsiniz. Nasıl yapılacağını buradan öğrenebilirsiniz. 
SQL Server platformunda Typed DataSet kullanmaya alışmış ve tüm alt yapısını SQL Server üzerine kuran bir yazılımcı Oracle Platformuna geçince ne olur? Bir süre sudan cıkmış balık durumda kalır. :) En büyük veri tabanı üreticilerinden biri olan Oracle'ın .Net platformu için SQL Server kadar hızlı ve kolay bütünleşik bir kod geliştirme aracı ne yazık ki yok. VS içinde ki Server Explorer penceresinden Oracle Packet içeriklerini bile göremiyorsunuz. Odp.Net ile gelen Oracle Explorer penceresi ile ancak Oracle veri tabanınızın detaylarını görebilmektesiniz. Fakat el alışkanlığı ile burada ki nesleri sürükleyip Xsd dosyanıza bırakmak istediğinizde nesnelerin taşınamaz olduğunu görürsünüz. Google'da Oracle Typed DataSet üzerine derin araştırmalar yapıp en sonunda Oracle XSD dosyalarını bir sonra ki VS sürümünde destekleyeceğini açıklayan duyurusu ile tüm hayalleriniz yıkılır. Ama iş beklemez çözüm bulunmalı. Peki, Oracle üzerinde Typed DataSet için 'minimum kodlama ile' Data Access Layer nasıl olacak ki?
Önce DataAccesss Layer nasıl olacak? Typed DataSet kullanan programlarda çalışma mantığı: Veriyi çek, kullanıcıya işlettir ve veri tabanını System.Data.DataAdapter sınıfı ile güncelle şeklindedir. Typed Data Set kullanıyorsanız DataAccess Layer System.Data.DataAdapter nesnesinin bir uygulamasını içermek zorundadır. SQL platformunda bunu TypedDataSetGenerator yaptığı için problem yok. Oracle platformu için çözüm üretelim:

İş ihtiyacımız nedir? Oracle Packetler içinde olan Store Procedure'leri kendi projemiz içinde çağırabilmek ve Typed DataSet üzerinde kullanmak. (Zaten diğer Oracle nesnelerine VS içinden erişilmekte.) Tüm bunları sürekli aynı kodları tekrar etmeden yapmalıyız.

Çözüm: Kod tekrarı yapmamak için Generic Data Access Layer oluşturmak.  İhtiyacımız olan tüm Store Procedure'leri Typed DataTable sınıflarına attribute olarak atamak ve Generic Data Access Layer üzerinde bu attributeleri kullanarak gerekli işlemleri yerine getirmek.(Bir önceki konuda olduğu gibi bu konuda da Attribute sınıflar yardıma koşuyor.) Her zaman ki gibi kodu en üst seviyede yazmalıyız. Sürekli kendisini tekrar eden kodlama yerine en üst seviyede bir defa kod yazmak. Böylece DataAdapter nesnesine olan bağımlılı tek yerden ve kesin bir şekilde ortadan kaldırabiliriz.

Önce Store Procedure'leri tanımlayacağımız Attributeleri yazalım:

 Store Procedure Attribute sınıflarımızı Typed DataTable sınıflarımıza ekleyelim.
[cs]
[SelectProcedure("ACME.PCK_TEST.PRC_GET_ALL_TEST", "refcursor P_TEST:", DefaultSelect = true ) ] 
[SelectProcedure("ACME.PCK_TEST.PRC_GET_TEST_BY_ID","ID1",DefaultSelect=false)] 
[InsertProcedure("ACME.PCK_TEST.PRC_INSERT_TEST", "NAME1", "VALUE1", "out P_ID1 : Int32 id1")] 
[UpdateProcedure("ACME.PCK_TEST.PRC_UPDATE_TEST", "NAME1","VALUE1","ID1")] 
[DeleteProcedure("ACME.PCK_TEST.PRC_DELETE_TEST","ID1")] 
partial class TEST1DataTable 
{ 

}
Neden birden çok Select Procedure var?  DataAdapter nesnesi her bir kayıtın RowState özelliğine göre kendisine ait InsertCommand, UpdateCommand, DeleteCommand fonksiyonlarında biri kullanılarak veriyi güncelleyecektir. Bu sebebten dolayı sadece birer tane Insert, Update ve Delete Store Procedure eklenmesine izin verilmiştir. Birden cok SelectCommand gerekecektir.
Böylece artık Typed DataTable nesneleri Oracle Packet üzerinde ilişkili oldukları Store Procedure'leri bilmektedir. DataTable nesneleri için artık bir Generic DataAdapter nesnesileri oluşturabiliriz. Veri şemasını biliyoruz, veriyi veri tabanında işleyecek Store Procedure'leri biliyoruz o halde Data Access Layer üzerinde sürekli aynı kodları yazmanın gereği var mıdır? En tepeye Generic bir Data Access Layer yazar ve tüm Logic içerisinde bu sınıfı kullanırız. (İp ucu: Yerinizde olsam Base Logic sınıflarınıda Generic yapardım. Bu sayede bütünleşik ve tamamen Generic bir çatınız olur. Projelerinizi minumum kodlama ile bitebilirsiniz.
IDataService ile genel veri tabanı etkileşim ihtiyaçları ortaya konmuştur.

Bu işlemleri gerçekleştiren OracleDataService sınıfıdır ki kendisi projede tek başına Data Access Layer olmaktadır. OracleDataService sınıfı kendisine tip parametresi olarak verilen Typed DataTable nesnesinin StoreProcedure Attribute'lerini almakta ve Odp.Net ile gelen OracleDataAdapter nesnesini bu Attribute'lere göre oluşturmaktadır.
[cs]
StoreProcedureAttribute[] sps = (StoreProcedureAttribute[])table.GetType()
 .GetCustomAttributes(typeof(StoreProcedureAttribute), false); 
foreach (StoreProcedureAttribute att in sps) 
{ 
 if (att is InsertProcedure) 
  InitInsertProcedure(att.ProcedureName, att.Paramters); 
 else if (att is UpdateProcedure) 
  InitUpdateProcedure(att.ProcedureName, att.Paramters); 
 else if (att is DeleteProcedure) 
  InitDeleteProcedure(att.ProcedureName, att.Paramters); 
 else if (att is SelectProcedure) 
  InitSelectProcedure(att.ProcedureName, att.Paramters,
   (att as SelectProcedure).DefaultSelect ); 
 else 
  InitCustomProcedure(att.ProcedureName, att.Paramters); 
}
Odp.Net OracleDataAdapter nesnesine ait Commandları oluştur:
[cs]
private Oracle.DataAccess.Client.OracleCommand 
  InitStoreProcedure(string procedureName, string[] parameters) 
{ 
 Oracle.DataAccess.Client.OracleCommand command = 
  new Oracle.DataAccess.Client.OracleCommand(); 
 command.Connection = _connection; 
 command.CommandType = System.Data.CommandType.StoredProcedure; 
 command.CommandText = procedureName; 
 foreach (string parameter in parameters) 
 {
  command.Parameters.Add(InitParameter(parameter)); 
 } 
 return command; 
}
Kullanımı gayet basittir:
[cs]
Demo.Framework.Generic.Data
 .OracleDataService 
_dataAccessLayer = new Demo.Framework.Generic.Data
 .OracleDataService(); 
public Form1() 
{ 
 InitializeComponent(); 
 _dataAccessLayer.FillAll(dataSet1.TEST1); 
} 

private void tEST1BindingNavigatorSaveItem_Click(object sender, EventArgs e) 
{ 
 _dataAccessLayer.UpdateTable(dataSet1.TEST1); 
}

Değişken parametrele Store Procedure çağrılarını gene OracleDataService sınıfını kullanarak yapabilirsiniz.

Sonuç

Tüm Data Access çağrıları sürekli aynı kodların tekrarı olmaktadır. Genelde manüel kodlama ile veya Code Genetor yazmak ile  bu sıkıcı kod bloğu oluşturulmaktadır. Yukarıda ki yaklaşım ile tekrar edilen kodlardan kurtulmuş olduk. Oracle ve Typed Dataset kullanan uygulamalar için genel (ve tecrübe ile sabit yeterli) bir Data Access Layer yazmış olduk. Aynı yaklaşımı SQL üzerinde de kullanabilirsiniz hatta sadece isim uzayını ve bir iki satır kodu değiştirerek aynı sınıfı kullanabilirsiniz. Fakat ben bunun yerine SQL ortamında size doğrudan XSD dosyalarını kullanmanızı öneririm.

30 Mart 2007 Cuma

Kendi Kendini Doğrulayan Typed DataRow

Verinin kendi kendini test etme yaklaşımı Validation Application Block ile iyice güçlendi. Artık Entity’ler daha zeki ve sakladıkları verinin doğruluğundan Entity nesnesi sorumlu. Validation Application Block veriyi Entity üzerinde saklayan programlar için mükemmel bir yaklaşım sunuyor. Fakat Typed DataRow üzerinde bir Application Block yok. Peki benim gibi üşengeç bir .Net yazılımcısı iseniz Entity nesneleri ile ilişkisel verileri saklamak zor geliyorsa, tüm verileri Typed DataSet içinde taşımak ve işlemek size daha kolay geliyorsa ne yapmalısınız?
Seçenek kalmadı kullandığınız Typed DataSet’leri kendi kendini test edebilir şekle getirmeliyiz. Ne gerekli bize:
  1. Atomik test işlemlerini yapan Attribute sınıflar.
  2. Bu test işlemlerini işletecek Controller sınıfı
Tabi tüm bu sınıfların veriden bağımsız olması yani Generic olması gerekmektedir. Burada ki kısıtlama Validation Application Block her bir Entity Field’a attribute vererek doğrulama yapılmakta fakat Typed DataRow üzerinden ki Field’lara Attribute kalıcı olarak eklenemez. Onun için bizim CheckAtiibute’lerimiz direk Typed DataRow nesnelerine eklenmelidir.
Doğrulama iki şekilde olur:
  1. Check: Veri kendi başına doğru mu sorusudur. Mesela email alanı gecerli mi? Para alanına negatif değer girildi mi?
  2. Validation: Veri iş kurallarına uygun mu sorusudur. Yani veri diğer veriler ile birlikte doğru mu?

Tüm işi gerçekleştiren Controller sınıfıdır. Check hatalarını girildiği anda (OnFly) yakalamak için DataSource almaktadır. DataSource üzerinden veri değiştiği zaman BaseCheck sınıfından türetilen Check kuralları çalıştırılmakta ve karşılaşılan hatalar ColumnError olarak atanmaktadır.

Önce takip edilecek Row’a Attributeler atanmalı: 
[cs]
[NotNullCheck("Column1",UserFriendlyName="Birinci alan")]
[MinimumCheck("Column2",12, UserFriendlyMessage = "İkinci alandaki veri 12 den küçük olamaz")] 
[UniqueCheck("Column3", UserFriendlyMessage = "İkinci alandaki veri tüm tabloda unique olmalı")]
partial class DataTable1Row 
{   

}
Veri değişimi takibi:
[cs]
#region FollowTable 
protected virtual void FollowDataSource(DataTable table) 
{ 
 table.ColumnChanging += 
  new DataColumnChangeEventHandler(table_ColumnChanging); 
} 

protected virtual void table_ColumnChanging(object sender, DataColumnChangeEventArgs e)
{ 
 // veri değişti check kurallarını işlet 
  if( e.Row.RowState != DataRowState.Deleted) 
  e.Row.SetColumnError(e.Column, 
   CheckRow(e.Column, e.Row, e.ProposedValue)); 
} 
#endregion
Değişen veriyi test et:
[cs]
#region check 
///  
/// kolon verisi değişti kontrolü 
///  
/// değişen kolon 
/// kullanılan row 
/// yeni değer 
/// hata iceriyorsa hata mesajı. hata yoksa boş string 
protected virtual string CheckRow(DataColumn dataColumn, DataRow dataRow, object columnNewValue) 
{ 
 dataRow.EndEdit(); 
 string result = string.Empty; 
 BaseCheck[] attrs = (BaseCheck[])dataRow.GetType()
  .GetCustomAttributes(typeof(BaseCheck), true); 
 foreach (BaseCheck attr in attrs) 
 {  
  if (attr.ColumnName == dataColumn.ColumnName) 
  { 
   result += attr.IsValid(dataRow, columnNewValue); 
  } 
 } 
 return result; 
} 
#endregion 
Check işleminin çalışma zamanı görüntüsü:

IsValid çağrısı ile önce Check kontrolleri çalıştırılır eğer tüm checkler doğru ise kontrol edilen Row HasSelfValidation attribute’ne sahip mi kontrolü yapılır. Eğer Row HasSelfValidation attribute’ne sahip ise SelfValidation attribute’ne sahip fonksiyonlar aranır ve bu fonksiyonlar çağırılır.
[cs]
#region validate 
///  
/// Veri tabanına kayıt etmeden önce kayıtın doğruluğunu kontrol et 
/// hata bulunur ise hatayı throw et 
///  
/// kontrol edilecek kayıt 
public virtual bool IsValid(DataRow row) 
{ 
 row.EndEdit(); 
 _errorMessage = string.Empty; 
 if (row.RowState != DataRowState.Deleted 
  && row.RowState != DataRowState.Detached) 
 { 
  foreach (DataColumn col in row.Table.Columns) 
   _errorMessage += CheckRow(col, row, row[col]);                       
   //tüm childe row'ları valid mi 
  foreach (DataRelation relation in row.Table.ChildRelations) 
   foreach (DataRow childeRow in row.GetChildRows(relation)) 
    foreach (DataColumn col in childeRow.Table.Columns) 
     _errorMessage += 
      CheckRow(col, childeRow, childeRow[col]); 
 } 
 // validation 
 if (_errorMessage.Length == 0 && row.RowState != DataRowState.Deleted 
  && row.RowState != DataRowState.Detached) 
 { 
  HasSelfValidation[] attrs = (HasSelfValidation[])row.GetType()
   .GetCustomAttributes(typeof(HasSelfValidation), false); 
  if (attrs != null && attrs.Length > 0) 
  { 
   foreach (MethodInfo method in row.GetType().GetMethods()) 
   { 
    SelfValidation[] validation = (SelfValidation[])method
     .GetCustomAttributes(typeof(SelfValidation), false); 
    if (validation != null && validation.Length > 0) 
    { 
     object result = method.Invoke(row, new object[] { }); 
     if (!string.IsNullOrEmpty((string)result)) 
     { 
      this._errorMessage += (string)result; 
     } 
    } 
   } 
  } 
 } 
 return string.IsNullOrEmpty(_errorMessage); 
} 
#endregion 
Validation kontrollerinin çalışma zamanı görüntüsü: