RSS Feeds

20 Kasım 2010 Cumartesi

MVVM II : Hello World

Serinin önceki yazıları

MVVM serisinin ikinci bölümüyle birlikte konuya hızlı bir şekilde kaldığımız yerden devam ediyoruz. Bir önceki bölümde MVVM’in ne tür bir mimari desen olduğu, temel bir kaç kuralı, avantajları ve dezavantajları gibi noktalara değinmiştik. Bu bölümde ise, basit bir “Hello World” uygulaması üzerinden View ve ViewModel kavramlarını anlamaya çalışacağız. Ayrıca View ve ViewModel arasındaki çok önemli iki etkileşim olan Notifiable Property ve Command’in nasıl oluşturulduklarını da öğreneceğiz.

Örnek uygulama Visual Studio 2010 IDE ’si üzerinde Silverlight 4 ile gerçekleştirilecek ve MVVM Toolkit’de yer alan kütüphaneler kullanılacaktır. Dolayısıyla başlamadan önce Toolkit’in kurulması gerektiğini hatırlatmakta fayda var. Laurent Bugnion’ın hazırladığı MVVM Toolkit’i ile ilgili daha ayrıntılı bilgi almak için serinin ilk yazısını incelemenizi tavsiye ederim.

Artık hazır olduğumuza göre, MVVM dünyasına giriş yapmak üzere “Hello World” projemizi oluşturabiliriz. Bu ilk örnekte, felsefenin aslını daha rahat görebilmek için, az sayıda kontrol ve iş içeren, oldukça sade bir arayüz üzerinden hareket edeceğiz. Hedefimiz ise, kullanıcının Button’a her tıklamasında, TextBlock’taki sayının bir arttığı basit bir Silverlight uygulaması geliştirmek. Örneğe geçmeden önce izlenecek yöntemle ilgili birkaç noktanın da altını çizmekte fayda var. İçinizden “Ne var bunda, + butonuna çift tıklar, oluşan metoda birkaç satır kod ekler ve hallederim” dediğinizi duyar gibiyim; ancak asıl amaç bahsi geçen bu minik programı MVVM mimari desenini takip ederek geliştirmek.

1

Klasik yaklaşım düşünüldüğünde, bu basit örnek, aşağıdaki adımlar izlenerek rahatlıkla yapılabilir.

  1. Button kontrolünün Click olayına bağlanmak üzere, sayfanın içinde bir metot oluştur.
  2. Oluşturulan metotta, TextBlock kontrolünün Text özelliğini okuyarak, mevcut değeri bir tamsayı değişkene ata.
  3. Değişkende saklanan değeri bir arttır.
  4. Yeni değeri string’e dönüştür ve tekrar TextBlock kontrolünün Text özelliğine ata.

Bu durumda yazılması gereken metot da aşağıdakine benzer olacaktır :

private void button1_Click(object sender, RoutedEventArgs e)
{
    int deger = int.Parse(textBlock1.Text);
    deger++;
    textBlock1.Text = deger.ToString();
}

Yukarıdaki metodun yazılması ve Click olayına bağlanmasıyla, hiç şüphesiz ekranda görünen sayının artması sağlanır; ancak söz konusu proje daha büyük olduğunda, izlenen bu yolun birçok dezavantajı bulunmaktadır. Arayüzde gerçekleşen işlerin (Business Logic) kodlanmaları sırasında kontrollere erişmek ve kontrolleri hedefleyen kodlar çalıştırmak bahsi geçen dezavantajların temelini oluşturmaktadır. Herşeyden önce bu yaklaşımın izlendiği projelerde, tasarımcılar ile programcıların bir arada çalışmaları daha güçtür. Örneğin, tasarımcı XAML içerisinde bir elemente Name niteliğini uygulayarak yeni bir isim verdiğinde, programcının kod tarafında ilgili elemente erişmek için kullandığı field’ın da adı değişecektir, dolayısıyla tasarımcının yaptığı basit bir isim değişikliği dahi problem oluşturabilmektedir. Bir başka örnek : Tasarımcı bir DataGrid’in kolon sırasını değiştirdiğinde, programcının ilgili kolonlardaki değerleri okumasında, kolon indeksleri değiştiğinden bir takım hatalar meydana gelebilir; hatta farkedilmesi güç, mantıksal hatalar dahi oluşabilir. Bunun yanı sıra, bahsi geçen klasik yöntemin, “önce A yapılır biter, sonra B başlar” yerine, Agile yaklaşımların benimsendiği projelerde daha fazla can sıktığını da söyleyebilirim.

2

Tabi ki dezavantajlar, yalnızca tasarımcı – programcı entegrasyonuyla da ilgili değildir. Klasik yaklaşım izlenerek oluşturulan bir projede, programcı hem kontrollere hem de diğer nesnelere ilişkin kodlamalar yapmaktadır. Dolayısıyla programcı, çekirdek iş mantığı bir yana; arayüzde yer alan kontroller, giriş değerleri için yapılan tip dönüşümleri, hesaplanan sonuçların kontrollere bağlanmaları vb. bir çok yan işlemi de düşünmek ve ele almak zorundadır. Hal böyle olunca, kişi, iş mantığından uzaklaşabilir, hatalar yapabilir ve basit bir takım geliştirmeleri yaparken bile gereksiz vakit harcayabilir.

MVVM tam olarak bu noktada devreye girmekte ve kodlamanın, tasarımdan bağımsız hale getirilmesi için bir çözüm sunmaktadır.  Temel hedef, iş mantığı ile görsel elementlerin bağımsızlığını sağlamak olduğundan, MVVM öncelikle iş mantığı ile görsel elementleri iki keskin kavramla birbirlerinden ayırmaktadır: View ve ViewModel. View, yalnızca görsel elementleri içermekte olup, tüm iş mantığı ViewModel’de yer almaktadır. Kilit nokta, ViewModel’in View’i tanımamasıdır. Diğer bir deyişle ViewModel’in kodlanması sırasında View’de yer alan kontroller dikkate alınmaz, hatta kontrollere erişilemez, yalnızca yapılan işlere odaklanılır.3

Klasik yaklaşım ve doğurduğu temel dezavantajları ile MVVM programcısının bakış açısından bahsettiğimize göre, uygulamanın MVVM deseni kullanılarak oluşturulmasına nihayet başlayabiliriz.

Hello MVVM

Yeni bir Silverlight Application projesi başlatarak, öncelikle MVVM Toolkit’ini kullanmak üzere gerekli kütüphaneleri referans edelim.

4

Ardından MainPage’i aşağıdaki XAML kodlarıyla oluşturalım.

<Grid x:Name="LayoutRoot" Background="White">
       <Button Content="+" Height="23" HorizontalAlignment="Left" Margin="64,92,0,0"  VerticalAlignment="Top" Width="75"  />
       <TextBlock Height="44" FontSize="30" HorizontalAlignment="Left" Margin="153,82,0,0" Text="TextBlock" VerticalAlignment="Top" />
</Grid>

ViewModel’in Oluşturulması

Yukarıdaki XAML kodlarından faydalanarak arayüzü tamamladıktan sonra, projeye,  MainPageViewModel olarak isimlendirdiğimiz bir Class ekleyelim. MVVM tasarım deseni düşünüldüğünde MainPage, View; MainPageViewModel ise onun ViewModel’i; diğer bir deyişle, MainPage’deki işlerin, elementlere erişmeksizin kodlandığı yer olacaktır.

public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase
{ }

Oluşturulan MainPageViewModel sınıfının, MVVM Toolkit kütüphanelerini referans ederek ulaştığımız ViewModelBase sınıfından kalıtıldığına da dikkat etmek gerekir. ViewModelBase, tüm ViewModel sınıflarının kalıtılacakları taban sınıftır ve her bir ViewModel’de ihtiyaç duyulan bir takım ortak üyeleri içermektedir.

5

ViewModelBase taban sınıfıyla ilgili bilinmesi gereken bir diğer özellik de, sınıfın, WPF ve Silverlight’ta veri bağlamasından hatırlayacağımız INotifyPropertyChanged arayüzünü uygulamış olmasıdır.

abstract class ViewModelBase : INotifyPropertyChanged, ICleanup, IDisposable
{ … }

Bu durumda oluşturulan MainPageViewModel sınıfı da kalıtım prensibi gereği, property değişiklerini arayüze haber verebilir niteliktedir. Yazının şu aşamasında çok da gerekli olmayan bu bilgi, örnek ilerledikçe çok önemli bir hal alacağından, şimdiden kulağa küpe yapılması şiddetle tavsiye edilirSmile


feather pen

MVVM Toolkit’i kullanmak yerine, kendi MVVM kütüphanenizi oluşturmanız halinde, tüm ViewModel’lere taban sınıf olma görevi gören ve INotifyPropertyChanged arayüzünü uygulayan ViewModelBase sınıfını da sizin sıfırdan oluşturmanız gerekir.



Bir ViewModel’in kodlamasına, View’de yer alan durum bilgileri ile kullanıcının tetikleyeceği işlemleri belirleyerek başlanmalıdır. Bu şekilde düşünüldüğünde, MainPage’de bir adet Int32 tipinden değerin tutulması gerektiği söylenebilir. Tabi ki bir de kullanıcı tarafından tetiklenmesi istenen, sayı arttırma işi söz konusudur. Özetle MainPageViewModel’de bir tane Int32 tipinden değer tutulması gerekirken, bir de bu değerin arttırılmasını sağlayan eylem bulunmalıdır. Belirlenen bu iki nokta, aslında ViewModel tasarımı açısından da iki yeni kavramı beraberinde getirmektedir:

    1. Notifiable Property : Arayüzde yer alan her bir durum bilgisi için oluşturulan ve değer değişikliklerini arayüze haber verebilen property’dir. Durum değeriyle aynı tipte oluşturulur. O halde örnekte kullandığımız MainPageViewModel sınıfına, Int32 tipinden bir Notifiable Property tanımlanması gerekmektedir.
    2. Command : Kullanıcının tetikleyeceği her bir “eylem” için oluşturulan, RelayCommand tipindeki özel property’lerdir. Her bir Command  tek bir işi ifade etmektedir. Dolayısıyla örnekte yer alan MainPageViewModel sınıfına, sayının arttırılmasına karşılık bir Command eklenmelidir.

6

Sayının değerini tutacak Notifiable Property’nin ViewModel’de tanımlanması için aşağıdaki kodların sınıfa eklenmesi yeterlidir.

public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase
{
       int _sayi;

       public int Sayi
       {
           get { return _sayi; }
           set
           {
               if (_sayi == value)
                   return;

               _sayi = value;
               //RaisePropertyChanged metodu, ViewModelBase sınıfında tanımlı bir metot olup, INotifyPropertyChanged arayüzüyle gelen PropertyChanged olayının tetiklenmesini sağlamaktadır. Böylece, arayüzde Sayi özelliğine bağlanmış elementlere değerin değiştiği haberi ulaşacaktır.
               base.RaisePropertyChanged("Sayi");
           }
       } 
}

Dikkat edilirse, sınıfa eklenen Sayi isimli üye, sıradan bir property olmasıyla birlikte, tek önemli farkı, RaisePropertyChanged metodu aracılığıyla meydana gelen değer değişikliklerini arayüze bildirmesidir. Böylece Sayi’ya bağlanmış elementler, sayının değişmesi durumunda bu değişiklikten haberdar olacaklar ve yenilenerek son değeri görüntüleyebileceklerdir.

Sayi property’sinin oluşturulmasından sonra geriye, yalnızca, sayının arttırılmasını sağlayacak Command’i tanımlamak kalmaktadır. Yukarıda da belirtildiği üzere ViewModel içerisinde bir Command tanımı yapmak için, RelayCommand tipinden bir property’nin oluşturulması gerekmektedir. Diğer bir deyişle her bir RelayCommand tipinden property, arayüzde, kullanıcının tetikleyeceği bir eylemi ifade etmektedir.


feather pen

MVVM Toolkit kütüphanelerinde yer alan ViewModelBase ve RelayCommand sınıfları, farklı MVVM kütüphanelerinde farklı isimlerle bulunabilirler.  Sınıf isimlerinden ziyade bu sınıfların görevlerini kavramak daha doğru olacaktır. ViewModelBase, tüm ViewModel’lerin taban sınıfıdır, RelayCommand ise kullanıcının tetikleyeceği her bir eylemi ifade etmek üzere kullanılan sınıftır.


Sayı arttırma işini ifade edecek Command’in oluşturulması için, MainPageViewModel sınıfına SayiArttirCommand isimli RelayCommand tipinden property aşağıdaki gibi eklenebilir.

//Command
public RelayCommand SayiArttirCommand { get; set; }

Tabi ki SayiArttirCommand tetiklendiğinde yapılacak işi de bir yerde tanımlamak gerekir. Bunun için ViewModel’in Constructor’ında bir RelayCommand nesnesi örnekleyerek komut oluşturulmalı ve çalıştırılması istenen metot belirtilmelidir.

7

Ekran görüntüsünden de farkedileceği üzere, RelayCommand nesnesinin örneklenmesi sırasında Action tipinden bir parametre gönderilmelidir. .NET Framework içerisinde yer alan Action tipi, void() imzasına sahip metotları işaret eden bir delege olup, bu örnekte, butona tıklandığında çalışması istenen metodun adresini saklamak üzere kullanılmaktadır. RelayCommand nesnesine parametre olarak geçilen bu metot, ViewModel sınıfında tanımlanabileceği gibi anonim olarak da yazılabilir. Bu bilgiler ışığında kodlar düzenlediğinde, Constructor metodun son şekli aşağıdaki gibi olacaktır.

public MainPageViewModel()
{
    SayiArttirCommand = new RelayCommand(() => Sayi++);
}


feather pen

Bazı durumlarda komut çalıştırılmadan önce bir takım koşulların sağlanıp sağlanmadığını kontrol etmek gerekebilir. Böyle bir ihtiyaç söz konusu olduğunda, geriye bool dönen bir metot tanımlayarak, RelayCommand’in oluşturulması sırasında Constructor’a ikinci parametre olarak belirtilir. Bu kullanım şekli, serinin devam eden yazılarında da incelenecektir.


Evet, nihayet ViewModel’in hazırlanması tamamlandığına göre, sınıfın son halini görebilir, bazı önemli noktaların altını çizebiliriz.

public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase

    int _sayi;
    public int Sayi
    {
        get { return _sayi; }
        set
        {
            if (_sayi == value)
                return;

            _sayi = value;
            base.RaisePropertyChanged("Sayi");
        }
    }

    public RelayCommand SayiArttirCommand { get; set; }

    public MainPageViewModel()
    {
        SayiArttirCommand = new RelayCommand(() => Sayi++);
    }
}

Dikkat edilmesi gereken önemli noktalar şunlardır :

  • Kodlamayı yaparken arayüzdeki elementlere erişmeyiz.
  • ViewModel’in oluşturulmasında tasarımdan bağımsız bir bakış açımız olmalı : “Bir Int32 değer var ve kullanıcının ekrandaki bir tetiklemesiyle bu değer birer birer artacak”
  • Arayüzde tutulan durum bilgileri için Notifiable Property oluşturulur.
  • Arayüzde kullanıcının tetikleyeceği sayı arttırma işi için Relay Command nesnesi oluşturulur.
  • Oluşturulan RelayCommand, kullanıcı butona tıkladığında çalıştırılacak metodu, Action delegesi aracılığıyla işaret etmektedir.

ViewModel’in View’e Referans Edilmesi

ViewModel’in tamamlanmasıyla, arayüze ilişkin tüm kodlama da tamamlanmıştır. Bu noktadan sonra tek yapılması gereken, View ile ViewModel’i arasındaki etkileşimleri belirtmektir. Tabi ki öncelikle View’in, kendisine ait ViewModel’i örneklemesi ve referans etmesi gerekmektedir. Bu amaçla, MainPage’in Constructor’ında aşağıda görülen düzenlemeler yapılmalıdır.

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        DataContext = new MainPageViewModel();
    }  
}

Aslında yapılan işlem oldukça basittir. MainPage, yani View, MainPageViewModel nesnesini, yani ViewModel’ini oluşturmuş ve sonrasında oluşan nesneye ait referansı DataContext property’sine atamıştır. Böylelikle MainPage’de yer alan elementler ile MainPageViewModel’de yer alan property’ler arasında, aşağıdaki şemada, oklarla belirtilen Binding ilişkileri kolayca oluşturulabilecektir.

8

Şemada görülen Binding ‘leri tanımlamak için tek yapılması gereken; TextBlock’un Text değeri ile Button’ın Command property’lerini, DataContext’te yer alan MainPageViewModel nesnesinin ilgili property’lerine Binding Markup Extensionını kullanarak bağlamaktır.

<Grid x:Name="LayoutRoot" Background="White">
    <Button Content="+" Height="23" HorizontalAlignment="Left" Margin="64,92,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SayiArttirCommand}" />
    <TextBlock Height="44" FontSize="30" HorizontalAlignment="Left" Margin="153,82,0,0"  Text="{Binding Sayi}" VerticalAlignment="Top" />
</Grid>

Hepsi bu kadarWink Gerekli Binding kurallarını belirttikten sonra uygulamanın son halini çalıştırırsanız, MVVM desenini kullanarak hedeflenen noktaya ulaştığımızı siz de test ederek görebilirsiniz.

9

 

Programın çalışma zamanındaki akışını ise aşağıdaki şemada inceleyebilirsiniz.

10

Böylece geldik bir makalenin daha sonuna. Bu yazıda çok basit bir Silverlight uygulamasını MVVM mimari desenini kullanarak oluşturduk ve konuya “Hello World” tadında bir giriş yaptık. Bir sonraki yazı itibariyle MVVM serisine kaldığımız noktadan hızla devam edeceğiz.

Faydalı olması dileğiyle…

14 Kasım 2010 Pazar

MVVM I : Hey, bu da nereden çıktı şimdi?

1

WPF ve Silverlight’ın arayüz programlaması için oldukça güçlü alternatifler olarak yazılım projelerinde yer almaya başladıkları şu günlerde, geliştirme yapan herkesin çok işine yarayacağını düşündüğüm seriye nihayet bu yazıyla birlikte başlıyoruz. Önümüzde uzun bir yol olacak ve bu uzun yol boyunca MVVM desenini geliştireceğimiz uygulamalar eşliğinde öğrenmeye çalışacağız. Serinin bu ilk yazısında ise, MVVM’in teknik olarak detaylı bir incelemesini yapmak yerine,  konuya kısa bir giriş yapacak ve bazı temel sorulara cevap arayacağız.

Bir yazılımda geliştirme yapan ekipler çoğu zaman en az iki kişiden oluşmakta olup, yazılan kod satırları da yine çoğu zaman birkaç bini aşmaktadır. Bu sebeple projede yer alan tüm üyelerin, geliştirme sürecinde, ortak kuralları kabul etmeleri ve bu kuralların doğurduğu kısıtlamalar dahilinde hareket etmeleri oldukça önemlidir. Böylece programcılar karşılaştıkları problemlere kendilerince çözümler getirmeleri yerine, önceden tanımlı desenleri kendi problemlerine uygulayarak, genel bir standartın oluşmasına olanak sağlarlar. 

Katmanlı mimari kullanılarak geliştirilen yazılımların sunum katmanları (Presentation Layer) için sıkça başvurulan tasarım desenleri aşağıda listelenmiştir :

  • Page Controller
  • Front Controller
  • Model-View-Controller (MVC)
  • Application Controller
  • Observer
  • Model-View-ViewModel (MVVM)Smile

BingoooooWink O halde MVVM’in sunum katmanının tasarlanmasında yararlanılan bir desen olduğunu söylemek doğru olacaktır. Hatta tam doğru bir tanım için şöyle söylenmelidir: “MVVM, WPF ve Silverlight uygulamalarında sunum katmanının oluşturulması için kullanılan bir mimari desendir.”

MVVM tasarım deseni Microsoft’un WPF takımından John Gossman tarafından yaratılmış olup, MVC’nin bir varyasyonu olarak düşünülebilir. Aynı zaman da Martin Fowler’ın PresentationModel ‘ine de oldukça benzemektedir.

MVVM’in temel hedefi, programda yer alan görsel elementler ile (UserControl, Page, Control, Window vb.), bu elementlerin kullanıcıyla etkileşimlerine ilişkin kodlamayı birbirlerinden en keskin şekilde ayırmaktır. Geleneksel arayüz programlamasında, bir ekrana ait iş mantıkları, o ekranın code-behind dosyasında kodlanırken, MVVM’de tam aksine code-behind ‘ın temiz kalması hedeflenir. “Nasıl yani?” dediğinizi duyar gibiyim, ancak bu sorunun cevabını bir sonraki yazıya bırakarak kaldığımız yerden devam edelim.

MVVM tasarım deseninin uygulanması

Bir WPF/Silverlight projesinde MVVM desenini kullanabilmek için, öncelikle bir MVVM kütüphanesi oluşturmak gerekir. MVVM kütüphanesi, tasarım deseninin projeye uygulanabilmesi için gerekli tipleri içerecektir. Bu noktada programcı ya kendi MVVM kütüphanesini sıfırdan geliştirmeli ya da var olan var olan kütüphanelerden birini tercih etmelidir. Çeşitli blog’larda ve Codeplex gibi açık kaynak kodlu projelerin paylaşıldıkları sitelerde farklı MVVM kütüphaneleri bulunabilmektedir. Bu kütüphanelerin tümü özünde aynı olmalarına rağmen, bazıları Code-snippet, Project Templates gibi ekstra özellikler sağlayarak ve önemlisi geliştirilmesi sürerek (yeni versiyonları yayınlanarak) ön plana çıkmaktadır.

Bizler bu yazı dizisinde gerçekleştireceğimiz projelerde MVVM kütüphanesi olarak, Laurent Bugnion’ın hazırladığı, http://www.galasoft.ch/mvvm/getstarted/ adresinden bilgi edinilebilen, açık kaynak kodlu MVVM Toolkit’ini kullanacağız. Bugnion’ın  MVVM Toolkit’i, desenin projeye uygulanması için gerekli DLL’lerin hem Silverlight hem de WPF versiyonlarına sahip olmakla birlikte, ayrıca işleri kolaylaştıran Visual Studio ve Expression Blend eklentilerini de içeriyor. Toolkit’in öne çıkan özelliklerini listelersek :

  • Hem Silverlight hem WPF için kullanılabilir olması
  • Kod yazımını çok kolaylaştıran çeşitli Code Snippet’lar
  • Geliştirilmeye devam edilmesi
  • Design-Time desteği
  • Dökümantasyon
  • Hem Visual Studio hem de Blend için proje şablonları
  • Windows Phone 7 geliştirmesi için VS2010 ve Blend desteği 

MVVM yazı dizisinde yapacağımız örnek projelerde bu toolkit’i kullanacağımız için, sizler de aşağıdaki adresten gerekli dosyaları indirerek, projeleri kendi bilgisayarınızda çalıştırabilir ve örnekleri bizzat test edebilirsiniz.

http://mvvmlight.codeplex.com/

Model-View-ViewModel

MVVM’in açılımı olan Model, View ve ViewModel’in  ne anlama geldiklerini zaten serinin devam eden yazılarında detaylı inceleyeceğiz, ancak bu giriş yazısında, kavramların biraz daha oturması amacıyla MVVM’i oluşturan baş harflerden bahsetmekte fayda var.

  1. View : Programın arayüzünü oluşturan formlar, MVVM’de View olarak anılmaktadırlar. Bir View, Silverlight Page, UserControl, Window, DataTemplate vb. herhangi bir tip olabilir. Önemli olan, nesnenin, çalışma anında bir görüntü üretiyor oluşudur.
  2. ViewModel : Bir View’ın, tüm iş mantığını barındıran tipe ViewModel denmektedir. Özetle View ile Model arasındaki bağlantıyı sağlar ve View’in durum bilgisini korur. ViewModel felsefesi şunu amaçlar : “Bu sınıfta kontrollere erişmeden, dolayısıyla kontrolleri hiç düşünmeden sadece iş mantığını kodla!” .
  3. Model : Basit bir ürün düzenleme formunu (View) kodlarken (ViewModel), bir Urun sınıfının oluşturulması ve ürünlere ait özelliklerin, property olarak Urun sınıfına eklenmesi söz konusudur. Bu durumda  View’’de yer alan elementleri, her biri birbirinden bağımsız bir int, bir string bir decimal’a değere bağlamak yerine, bir Urun nesnesine bağlanması çok daha doğrudur. Bu durumda yazılan Urun sınıfının Model sınıf olduğu söylenebilir. Model sınıfların özel olarak bir takım Attribute’larla işaretlenme zorunluluğu da yoktur. Diğer bir deyişle POCO sınıfı dahi bir Model olarak olarak kullanılabilir; ancak çoğu zaman model sınıflar ya servis referansının eklenmesiyle otomatik üretilir ya da bir ORM aracıyla oluşturulur.

4

Model, View ve ViewModel ile ilgili bilinmesi gereken çok önemli iki altın kuralı da atlamamak gerekir. Bunlar :

  1. View, ViewModel’i bilir, fakat tersi söz konusu değildir.
    View’de bulunan elementler, ViewModel’de tanımlı üyelere Bind edileceklerinden, View’in ViewModel’i bildiğini söyleyebiliriz; ancak ViewModel kesinlikle View’de yer alan elementlere erişmez ve View’de ne olduğunu bilmez.
  2. ViewModel, Model’i bilir, fakat tersi söz konusu değildir.
    ViewModel, Model’in, örneğin bir Urun sınıfının üyelerine erişebilir ve kullanabilir, yani kısaca ViewModel, Model’i bilir; fakat Model’de yer alan sınıf içinde ViewModel’le ilgili herhangi bir kod bulunmaz.

MVVM’in Avantajları

  • ViewModel kodlama alışkanlığı, geliştiriciye, iş mantığına odaklanma zorunluluğu ve disiplinini kazandırır.
  • Arayüzde verilerin alınmasını (input) ve görüntülenmesini (output) sağlayan kontroller değişse de iş mantığı değişmedikçe ViewModel’de bir değişiklik yapılmasına gerek yoktur.  Sonuç olarak, Görünüm ve İşler’in keskin bir şekilde ayrılması hem yönetilebilirliği arttırır hem de olası bir değişiklikliğin daha kolay uygulanabilmesini sağlar.
  • MVVM tasarımının uygulandığı WPF ya da Silverlight uygulamalarında Expression Blend’in becerileri  daha etkin kullanılabilmektedir. View’in “Blendability” ‘si yükselirSmile
  • XAML içinde yapılan Binding’ler ile, kontrollere değer bağlama işlemi oldukça kolaylaşmaktadır. MVVM, Binding kullanımlarını en üst düzeye çıkartarak, kod dosyasındaki veri bağlama işlerini kod tekrarı olmadan ve çok konforlu bir biçimde yapılmasını sağlar.
  • İş mantığı arayüzden bağımsız olduğundan, programın test edilmesi çok daha kolaydır. Yani daha bir  “Testable” ‘dırSmile
  • Tasarımcılar ile programcıların aynı projede eş zamanlı çalışmalarını daha etkili hale getirir.

MVVM’in Dezavantajları

  • ViewModel’de kontrollere erişememek, zaman zaman işi zorlaştırabilir. Bu tarz durumlarla çok karşılaşmamak için MVVM’i iyi anlamak ve benzer problemlere nasıl çözümler önerildiğini okumak, araştırmak gerekir.
  • Zaman zaman yazılan kod satır sayısının artmasına sebep olabilir; ancak code snippet’ları kullanmaya alıştığınızda bu durum artık bir dezavantaj olmayacaktır.

Serinin bir sonraki yazısı itibariyle, örnekler üzerinden MVVM’i konuşmaya ve avantajlarını anlamaya devam edeceğiz. Öğrendiklerinizi kısaca bir gözden geçirmek üzere, MVVM’in temel kavramlarına kısaca değinmiş olduğumuz bu bölümdeki bazı sorulara göz atabilirsiniz.

  • MVVM nedir?
  • Model, View, ViewModel kavramları ne anlama gelir?
  • Model, View ve ViewModel arasındaki iki önemli kural nelerdir?
  • Benzerlik gösterdiği diğer tasarım desenleri nelerdir?
  • MVVM Toolkit nedir?
  • Desenin avantaj ve dezavantajları nelerdir?

Faydalı olması dileğiyle…

8 Kasım 2010 Pazartesi

At The Edge Of Time

1

Bugün blog’a şöyle bir baktım da yazılım dışında pek birşey paylaşmamışısız. Bu sebeple, biraz değişiklik olması iyi olur diye düşündüm ve müzik yazmaya karar verdimSmile

Birçok farklı müzik türünü dinlesem ve sevsem de, ağırlıklı olarak Rock/Metal dinlerim. Bu müzik türünde de Blind Guardian’ın benim için ayrı bir yeri olduğunu itiraf etmeliyim.

Sevenleri bilir, çok sık albüm çıkarmaz bu arkadaşlar, 4 yılda bir yaparlar ama tam yaparlar. Grubun 2006 yılında yayınladığı “A Twist in the Myth” albümünden yine 4 yıl sonra,  yeni albüm “At the Edge of Time” 2010 Ağustos ayının başlarında yayınlandı.

"Sacred Worlds" — 9:17
"Tanelorn (Into the Void)" — 5:58
"Road of No Release" — 6.30
"Ride into Obsession" — 4.46
"Curse My Name" — 5:52
"Valkyries" — 6:38
"Control the Divine" — 5:26
"War of the Thrones" — 4:55
"A Voice in the Dark" — 5:41
"Wheel of Time" — 8:55

Gördüğünüz gibi, parçalar genelde uzun sayılabilecek süredeler. Özellikle 9:17’lik Sacred Worlds ve 8:55’lik Wheel of Time bu anlamda dikkatleri çekiyorlar. Bu iki parçanın neden açılış ve kapanış parçaları olarak seçildiklerini ise albümü dinlemeye başladığınızda anlıyorsunuz.

Albümdeki Wheel Of Time, tartışmasız favorim oldu. Doğu ezgileri, orkestra ve Hansi’nin sesi birleşince destansı bir parça çıkmış ortaya. And Then There Was Silence ile birlikte Blind Guardian’ın mihenk taşlarından biri kesinlikle.

Curse My Name, Tanelorn, Road of No Release, War of the Thrones, Control the Divine ve A Voice in the Dark da albümde en sık dinlediğim parçalar olarak MP3 oynatıcımda yerlerini aldılar:)

Bu arada unutmadan, ozanların 2 Nisan 2011’de tekrar Istanbul’da olacağını hatırlatmakta fayda var. Biletler de satışta :

http://www.biletix.com/event.htm?id=MLVMA

25 Ekim 2010 Pazartesi

Metadata Sınıfları ve Shared Kodlar

WCF RIA Servislerindeki en sevdiğim noktalardan bir tanesi, veri modelinde yer alan Entity'lere ilişkin bazı işlerin Metadata sınıflarına yıkılabilmesidir. İlk bakıldığında yalnızca bir takım basit validasyonları sağlayan Attribute’ların burada yer alacağı sanılabilir; fakat üretilen Metadata sınıflarında yapılabilecekler sadece bunlar değildir. Bu yazıda konuşacağımız bir başka konu olan Shared kodlar ise, hem istemci hem de sunucu taraflarında, Entity’lere, özelleştirilmiş iş mantıklarının kazandırılmalarına yönelik bir kullanım sağlamaktadır. Dilerseniz konuya geçmeden önce, bu yazıda değineceğimiz başlıklara kısaca bir göz atalım :

  • Önceden tanımlı validasyon kuralları
  • İstemci taraflı özelleştirilmiş validasyon
  • Entity sınıfına özelleştirilmiş bir iş mantığı yüklemek
  • Veri okumalarında servisten ilişkisel satırların çekilmesi

Tüm bu senaryoları incelemek üzere yeni bir Silverlight uygulaması oluşturarak işe koyulalımWink

Örnek projede veri kaynağı olarak AdventureWorks’ün Product, ProductSubcategory ve ProductCategory tablolarını kullanacağız. Bu üç tabloyu içeren Entity Data Model ve hemen ardından Domain Service Class’ı projeye dahil ettiken sonra Domain Service ve projenin görüntüsü aşağıdaki gibi olmalıdır. Bu noktada özellikle Generate associated classes for metadata seçeneğinin açık olduğuna dikkat edilmelidir. Çünkü bu seçeneğin işaretli hale getirilmesiyle ,veri modelinde bulunan Entity sınıfları için metadata sınıfları oluşturulacaktır.

1

2


questionmark Nedir bu metadata sınıfları?

Öncelikle AdventureWorksService.cs isimli dosyada, servis tarafında çalışacak metotların yer aldığını hatırlatmak gerekir. Projede yer alan AdventureWorksService.metadata.cs ismindeki dosya ise, bahsedilen metadata sınıflarını içermektedir.

Metadata sınıfı ne işe yarar?

WCF RIA Servislerinde metadata, yazının başında listelenen senaryoların birçoğunda sıklıkla kullanılmaktadır. Ağırlıklı olarak validasyon kısmının bu sınıflarda yer alacağı söylenilebilir.

Peki neden Product sınıfı varken, bu işlerin ProductMetadata sınıfında yapılması gerekiyor?

Çok basit. Çünkü Product sınıfı, Entity Framework’ün Code Generator’ı tarafından oluşturulmaktadır. Dolayısıyla üzerinde değişiklik yapılırsa, Code Generator’ın kodları baştan oluşturması durumunda bu dğeişiklikler kaybolacaktır. Bu yüzden benzer kod üretim durumlarının tümünde, kod üretimiyle oluşan sınıflarda değişiklik yapmak yerine, Partial Class ve Partial Method tekniklerinden faydanılmalıdır.


Tabi ki senaryoları test edebilmek için, Silverlight istemcisinde, ürün kayıtlarının güncellenebileceği basit bir arayüze de ihtiyaç olacaktır. Örnekte kullanılan Silverlight uygulamasının çalışma zamanındaki görüntüsü aşağıdaki gibidir.

3

A) Önceden tanımlı validasyon kuralları

Bu validasyonlar System.ComponentModel.DataAnnotations.ValidationAttribute tipinden kalıtılarak oluşturulmuş Attribute sınıfları sayesinde gerçekleştirilmektedirler. Bu sınıfların RIA Servisleri ve Silverlight’a özel olmadıkları da unutulmamalıdır. Bahsi geçen sınıflar, validasyon amacıyla ASP.NET Dynamic Data uygulamalarında da kullanılabilmektedirler. ValidationAttribute sınıfından kalıtılmış bu Attribute’lar aşağıda listelenmiştir.

  • CustomValidationAttribute
  • DataTypeAttribute
  • RangeAttribute
  • StringLengthAttribute
  • RequiredAttribute
  • RegularExpressionAttribute

Listelenen validasyon kurallarının Entity sınıflarına uygulanmaları metadata sınıfllar üzerinden gerçekleştirilir. Örneğin, ürün adlarında alpha numeric karakterler bulunamaması için ProductMetadata sınıfında, aşağıdaki gibi bir validasyon bildirimi yapılabilir.

[RegularExpression(@"^[a-zA-Z0-9_\s]*$", ErrorMessage = "Ürün adı, alfanümerik karakterler içeremez.")]       
public string Name { get; set; }

Hatta bu bildirimler birlikte de kullanılabilirler.

[RegularExpression(@"^[a-zA-Z0-9_\s]*$", ErrorMessage = "Ürün adı, alfanümerik karakterler içeremez.")]
[Required(ErrorMessage = "Ürün adı boş geçilemez.")]
public string Name { get; set; }

Uygulamanın çalışma anındaki görüntüsü :

4

Önceden tanımlı RangeAttribute, StringLengthAttribute gibi diğer validasyon sınıfılarının kullanımları da benzer olduklarından bu kullanımlara ait testleri size bırakıyorum.

 


Peki ben ProductMetadata isimli sınıfta, NVARCHAR(50) kolonuyla eşleşmiş
Name kolonuna [StringLength(
questionmark50)] niteliğini uygulamam da gerekir mi?
Her metinsel kolon için bunu tekrarlayacak mıyım?

Hayır, veri tabanındaki kolon bilgilerinden yola çıkılarak bu işlem biz farkında olmadan otomatik olarak zaten yapılıyor. Nasıl olduğunu anlamak için Silverlight projesinin Generated_Code klasörüne bakmak yeterli olacaktırWink

[DataMember()]
[RegularExpression("^[a-zA-Z0-9_\\s]*$", ErrorMessage="Ürün adı, alfanümerik karakterler içeremez.")]
[Required(ErrorMessage="Ürün adı boş geçilemez.")]
[StringLength(50)]
public string Name
{
    get
    {
        return this._name;
    }
    set
    {
        if ((this._name != value))
        {
            this.OnNameChanging(value);
            this.RaiseDataMemberChanging("Name");
            this.ValidateProperty("Name", value);
            this._name = value;
            this.RaiseDataMemberChanged("Name");
            this.OnNameChanged();
        }
    }
}

Bu property istemcide oluşan Product sınıfına aittir. Yani servisteki ProductMetadata sınıfında StringLength niteliği uygulanmamış olsa bile, kolonun tipi NVARCHAR(50) olduğundan dolayı istemcide üretilen sınıfta ilgili işaretleme otomatik olarak gerçekleştirilmiştir. Kaldı ki sadece StringLength değil, Key, RoundtripOriginal, Association gibi nitelikler de gerekli olan property’ler üzerine otomatik olarak uygulanmaktadır.


 

B) İstemci taraflı özelleştirilmiş validasyon

Zaman zaman mevcut validasyon teknikleri ihtiyaçları karşılamayabilir. Bu durumda kendi validasyon metotlarımızı yazarak, Entity bazında doğrulamayı Silverlight uygulamamız içerisinde gerçekleştirmemiz gerekir. RIA Servisi içerisinde yer alan bazı iş mantıklarının, istemci tarafında da yer alması istendiğinde, servisin bulunduğu projeye, [DosyaAdi].shared.cs notasyonuyla bir dosya eklenmesi yeterlidir. Eğer Web uygulaması içinde bu notasyona uyan bir dosya var ise, bilinmelidir ki, içerdiği kodlar istemci tarafında, yani Silverlight uygulamasında da oluşturulacaktır. Örneğin AdventureWorksService.shared.cs isminde bir dosya, Web uygulamasına eklenerek proje derlendiğinde, aynı kod dosyasının istemcide de üretildiği rahatlıkla görülebilir.

5

Derleme işlemi sonrasında istemci uygulamanın görüntüsü aşağıdaki gibi olmalıdır (Eğer Generated_Code klasörü görünmüyorsa Solution Explorer’da Show All Files seçeneği açılmalıdır) .

6

Bingo! Madem ki, servis tarafında shared.cs uzantısına sahip bir dosya, istemcide de aynen üretiliyorsa, o halde servise eklenilen validasyon metodu istemci uygulamaya kolaylıkla paylaştırılabilir. O halde Web uygulamasına eklenilen AdventureWorksService.shared.cs dosyasının validasyon metotlarını saklamak üzere kullanılması, bizi çözüme götürecektir.

Yalnız bu noktada devam etmeden önce çok önemli bir durumu hatırlatmakta fayda vardır. Shared olarak işaretlenmiş dosyadaki kodlar, birebir olarak istemcide de oluşacağı için, yazılan kodların istemci uygulamada çalışabilir nitelikte olması gerekmektedir. Örneğin Web uygulamasına eklenmiş Shared dosyada şöyle bir kod bulunduğunu düşünelim :

using System.Xml.Linq;

Elbette, Web uygulamasına eklenen bu using bloğu, eğer Web uygulamasının Assembly referanslarında System.Xml.Linq.dll’i bulunuyorsa bir hataya sebep olmaz. Ancak aynı kodlar birebir olarak istemcide de oluşacağı için, System.Xml.Linq.dll’inin istemci Silverlight uygulamasını da referans edilmesi gerekir.

Tabi ki validasyon metodunun yazılmasında bazı kurallar da söz konusudur :

  • Metot public erişim belirleycisine sahip olmalıdır.
  • Metot static olmalıdır.
  • İkinci parametresi ValidationContext tipinden olmalıdır.
  • Dönüş tipi ValidationResult olmalıdır.
  • Metodun bulunduğu sınıf public erişim belirleyicisine sahip olmalıdır.
  • Metodun bulunduğu sınıf static olmak zorunda değildir.
  • Metodun ismi istenildiği gibi verilebilir.
  • Metodun ilk parametresi, doğrulanan değeri taşımaktadır. Bu yüzden parametrenin tipi, doğrulanan değerin tipiyle aynı yapılır.

Bu kurallar doğrultusunda, ProductNumber değerlerinin “0” ile başlamasını engelleyen basit bir validasyon metodu aşağıdaki şekilde yazılabilir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;

namespace RIA_EntityMetadata.Web
{
    public class MyValidationRules
    {
        public static ValidationResult IsValidProductNumber(string value, ValidationContext context)
        {
            if (value.StartsWith("0"))
                return new ValidationResult("Ürün numarası 0 ile başlayamaz.", new[] { context.MemberName });

            return ValidationResult.Success;
        }
    }
}

Son olarak yazılan doğrulama metodunun, ProductMetadata sınıfında ProductNumber özelliğine uygulanması gerekir. Bunun için, ilgili property’ye, CustomValidation niteliği uygulanmalıdır. Niteliğin uygulanması sırasında verilen ilk argüman, validasyon metodunun bulunduğu sınıfın tipini, ikinci argüman ise metodun adını belirtmektedir.

[CustomValidation(typeof(MyValidationRules), "IsValidProductNumber")]
public string ProductNumber { get; set; }

Uygulamanın çalışma anındaki görüntüsü :

7

C) Entity sınıfına özelleştirilmiş bir iş mantığı yüklemek

ORM araçlarının ürettikleri Entity sınıflarına zaman zaman, ilave bir takım metotlar ya da property’’ler eklemek gerekir. RIA Servisi kullanılan veri odaklı uygulamalarda da tabi ki benzer ihtiyaçlar söz konusu olabilmektedir. Hemen bu durumu da bir senaryo ile canlandırmaya çalışalım. Örneğin, ListPrice bilgisinin yazıldığı TextBox’ın yanına bir TextBlock ekleyerek, KDV dahil fiyat bilgisi görüntülenmek istense nasıl bir yol izlenmelidir?

Akıllara ilk gelen çözüm, Product sınıfına ListPrice değerini 1.18 ile çarpıp geriye dönen bir property tanımlamak olacaktır. Tabi ki, son derece doğru bir yaklaşımdır. Ancak, bu üye, Product sınıfına nasıl eklenmelidir?  Entity Framework’ün oluşturduğu bu sınıfa yeni bir üye eklense bile, aynı üyenin istemci tarafında oluşan Product sınıfına da kazandırılması nasıl sağlanacaktır?  Tabi ki, Shared işaretlenen bir kod dosyası ile! Smile

O halde öncelikle Web uygulamasına, istemciyle paylaşılacak olan, Product.shared.cs adındaki kod dosyasını ekleyelim.

8 

Daga sonra eklenen kod dosyasında, Entity Framework tarafından üretilen Product tipi için, partial sınıf tekniğiyle bir property tanımlayalım.

using System;
using System.Collections.Generic;
using System.Linq;

namespace RIA_EntityMetadata.Web
{
    public partial class Product
    {
        public decimal KdvDahilFiyat
        {
            get { return ListPrice * 1.18M; }
        }
    }
}

Artık istemci tarafındaki Product sınıfında da KdvDahilFiyat özelliği bulunmaktadır. Dolayısıyla Silverlight istemcisinde fiyat bilgisinin yazıldığı TextBox’ın yanına bir TextBlock ilave ederek, KdvDahilFiyat bilgisine Binding yapılabilir.

9

Bu teknik ile, yukarıdaki gibi basit bir property yerine, istemcide kullanılmak üzere daha karışık işler gerçekleştiren bir method, hatta event gibi üyelerin de tanımlanabileceği unutulmamalıdır.

D) Veri okumalarında servisten ilişkisel satırların çekilmesi

Metadata sınıfının kullanıldığı durumlardan biri de, servisten dönen bir Entity’nin ilişkisel Entity’leriyle birlikte yüklenmesidir. Böylece, ürün bilgileriyle birlikte, ürünün alt kategori bilgisi de istemciye tek seferde getirilmiş olacak, alt kategori bilgisini elde etmek için istemcinin ikinci bir talep yapmasına gerek kalmayacaktır. Söz konusu bu durumun, Eager Loading olarak da bilinen bir Entity yükleme stratejisi olduğunu da hatırlatmakta fayda vardır.

Entity Framework’de Eager Loading, Include isimli metot aracılığıyla gerçekleştirilir. Örnek bir kullanımı şu şekilde olabilir :

Product[] products = context.Products.Include(“ProductSubcategory”).ToArray();

Görüldüğü üzere, Include metodu parametre olarak, Product nesnelerinin hangi tablodaki ilişkisel satırları beraberinde yükleyeceğini istemektedir. Dolayısıyla, Domain Service sınıfında Product’ların istemciye döndürülmesini sağlayan GetProducts metodu düzenlenerek işe başlanmalıdır. Düzenleme sonrasında metodun görüntüsü şu şekilde olmalıdır :

public IQueryable<Product> GetProducts()
{
    return this.ObjectContext.Products.Include("ProductSubcategory");
}

GetProducts metodunda yapılan Include çağrısı sayesinde, Product tablosuyla ProductSubcategory tablosunun JOIN edilerek ilişkisel satırların bir defada SQL sunucusundan alınması sağlanmıştır. Aşağıda, Include m

SELECT
[Extent1].[ProductID] AS [ProductID],
[Extent1].[Name] AS [Name],
[Extent1].[ProductNumber] AS [ProductNumber], ...
FROM  [Production].[Product] AS [Extent1]
LEFT OUTER JOIN [Production].[ProductSubcategory] AS [Extent2] ON [Extent1].[ProductSubcategoryID] = [Extent2].[ProductSubcategoryID]

Fakat bu düzenleme tek başına yeterli değildir. Domain Service içinde de, Product nesnelerinin, ilişkisel ProductSubcategory entity’siyle birlikte yüklenerek istemciye gönderilmesi için ekstra bir düzenleme gerekmektedir. Söz konusu düzenleme için ProductMetadata sınıfına geçilerek, ilgili Navigation Property bulunmalı ve Include niteliğiyle işaretlenmelidir.

[Include]
public ProductSubcategory ProductSubcategory { get; set; }

Eğer ki Include niteliğinin ilgili özelliğe uygulanması es geçilirse, SQL sorgusu JOIN yapacak şekilde üretilecek ve SQL sunucusundan ürün bilgileri, ilişkisel alt kategori satırlarıyla birlikte elde edilecek; fakat Silverlight istemcisiyle RIA Servisi arasında gerekli konfigürasyon yapılmamış olduğundan, elde edilen verinin istemciye gönderilmesi gerçekleşmeyecektir.

Her iki düzenleme de başarılı bir şekilde yapıldıktan sonra, istemci uygulamaya bir TextBlock daha ekleyerek gerekli Binding işlemi gerçekleştirilebilir.

<TextBlock  Grid.Column="2"  Margin="5" VerticalAlignment="Center" HorizontalAlignment="Left" FontSize="15" Foreground="Red" Text="{Binding Path=ProductSubcategory.Name}"/>

10

Gördüğünüz gibi, WCF RIA Servislerinde Metadata sınıfları ve Shared kod dosyaları çok farklı amaçlara hizmet edebilmektedirler. Özellikle Shared dosyalarla ilgili çok farklı ve keyifli kullanımlar yaratılabileceğini düşünmekteyim. Hatta yine benzer şekilde deklarativ olarak bazı işlemleri gerçekleştirmek üzere kendimize özel Attribute sınıfları tasarlayabileceğimiz ve bu kullanımları daha da öteye götürebileceğimizi unutmamamız gerekir.

Faydalı olması dileğiyle…

4 Ekim 2010 Pazartesi

Veri Modelinin Doğru Tasarlanması ve Sağladığı Avantajlar

Gerek LINQ to SQL, gerekse ADO.NET Entity Framework, uygulamaların geliştirilmesinde kullanılırken öncelikle bir veri modeli oluşturulur. Bu veri modeli, LINQ to SQL’de uzantısı .dbml, EF’de ise .edmx olan dosyalardan ve beraberindeki kod dosyalarından ibarettir. Peki veri modelinin tam olarak görevi nedir? Bu soruyla ilk karşılaşıldığında hemen akla şu basit cevap gelir : “Veri modeli sayesinde çalışma anında CRUD operasyonlarında kullanılan SQL komutları üretilebilir.” Hmm, güzel, fakat tek başına yeterli bir ifade gibi görünmüyor. Gerçek şu ki, eğer veri modeli doğru tasarlanırsa, çok önemli faydalar sağlayabilir. Ayrıca esnek bir yazılım geliştirme ortamı yaratılmasında da büyük rol oynar.

1

Resimde de görüldüğü üzere veri modeli, veritabanı ile çalışan uygulama arasındaki bir katmandan başka birşey değildir. Bu model içerisinde çoğunlukla Entity sınıfları ile birlikte Stored Procedure ya da User Defined Function gibi programatik öğelere karşılık üretilen metotlar bulunmaktadır. Tabi ki bu noktada Entity sınıflarının da kendi üzerinde Identity Column, Primary / Foreign Key vb. bilgileri taşıdığının altını çizmek gerekir. Veri modeli aracılığıyla SELECT, INSERT, UPDATE, DELETE gibi komut cümleleri çalışma anında otomatik üretilebilir. Ayrıca sunucudan gelen sonuç kümelerinin Entity nesneleri halinde belleğe yüklenmeleri de yine veri modeli sayesinde olur. Fakat yazının da başında belirtildiği üzere, doğru oluşturulan bir veri modeli bundan çok daha fazlasını gerçekleştirebilmelidir.

Konuyu bir örnek üzerinde açıklamanın daha faydalı olacağı düşüncesindeyim. Amacımız Kitap ve DVD kayıtlarını tutmak üzere bir veritabanı tasarlamak ve sonrasında da bu veritabanına Entity Framework aracılığıyla erişmek olsun.

Kitap

DVD

UrunID UrunID
Ad Ad
Fiyat Fiyat
KdvOran KdvOran
Yazar Yonetmen
SayfaSayi Sure
BasimYil Tur

Bu varlıkları içerecek bir veritabanı tasarımında da tabi ki farklı seçenekler söz konusudur. Bu seçenekler, özellikle sorgu performansı, veri tutarlılığı ve normalizasyon kuralları gibi çeşitli faktörlerin etkisiyle meydana gelmektedir. Geliştirilecek örnek, bu seçeneklerden iki tanesini içerecek olup, Entity Data Model’in yazılım projelerine kazandırdığı esnekliği daha iyi anlamamızı sağlayacaktır.

Seçenek 1 : Table Per Type Model

2 

Birinci veritabanı tasarımında, adından da belli olduğu üzere her varlığa karşılık bir tablo oluşturulmuş ve bu sayede Kitap ve DVD varlıklarına ait ortak kolonlar Urun tablosunda tanımlanarak, UrunID,Ad,Fiyat ve KdvOran kolonlarının tüm tablolarda tekrarının önüne geçilmiştir. Hmmm, güzel. Tabi ki bu tasarımın çeşitli avantaj ve dezavantajları olduğunu belirtmek gerekir.

Avantajları

  • Kolonlar her tabloda tekrar ettirilmez
  • Gerektiği taktirde kolonlara NULL kısıtlaması getirebilir

Dezavantajları

  • Bir varlığın bilgileri temel bilgileri Urun, kendine özel bilgileri ise ikinci bir tabloda bulunduğundan, dolayısıyla yapılan JOIN’lerden dolayı sorgu performansı düşer

Konumuz bu tasarımların doğruluğunu yanlışığını tartışmak olmadığından, avantaj ve dezavantajlar üzerinde daha fazla durmadan ikinci tasarımı inceleyelim ( Table Per Type modeliyle ilgili daha ayrıntılı bilgi için internette çokça döküman bulunabilmektedir ) .

Seçenek 2 : Table Per Hierarchy Model

3 

İkinci tasarımda ise yalnızca tek tablo bulunması, dikkatleri ilk çeken noktadır. Bu tabloda bulunan UrunTip kolonu ise, kayıtlı ürünün hangi türden (DVD ya da Kitap) bir ürün olduğunun bilgisini içermektedir. Hmmm, e bu da güzelSmile Kısaca, avantaj ve dezavantajlarından da bahsetmek gerekirse :

Avantajları

  • JOIN işlemi olmadan bilgiler elde edildiğinden sorgu performansı daha yüksektir.

Dezavantajları

  • Tüm kolonlar tek tabloda bulunduğundan, eğer ürün tipleri ve bunların özellikleri çok sayıda olursa kolon sayısı çok fazla artabilir. Hatta SQL Server’ın kolon limitine ulaşabilir.
  • Bir kayıt girilirken tüm kolonlar değer almayacağı için, ortak kolonlar dışındakilerin tümü boş geçilebilir ( nullable ) bırakılmalıdır. Bu da veri tutarlılığı açısından bir dezavantaj olarak düşünülmelidir.

İlk akla gelen avantaj ve dezavantajları sıraladıktan sonra, tekrardan konumuzun bu tasarımları tartışmaktan ziyade veri modeli olduğunu hatırlatarak örneğe kaldığımız noktadan hızla devam edelim ( Table Per Hierarchy modeliyle ilgili daha ayrıntılı bilgi için de internette çokça döküman bulunabilmektedirSmile ) .


Örnek Proje

Yazının başında, satır arasından yakalanması gereken, belki de en önemli nokta, Entity Data Model’in esnek bir uygulama ortamı yaratmasındaki rolüdür. Bu rolü daha iyi analiz edebilmek ve doğru bir modellemenin uygulamanın kodlanması sırasında ne gibi yararlar sağladığını anlamak için, hem TPT hem de TPH tasarımlayıla oluşturulan iki farklı veritabanını EF ile kullanmaya çalışacağız.

Geliştireceğimiz örnek proje, basit bir Console uygulaması olacak ve oluşturacağımız projeye iki Entity Data Model ekleyeceğiz. Bu modellerden biri TPT tasarımlı veritabanı, diğeri ise TPH tasarımlı veritabanına erişim sağlayacak. Bu durumda projenin, aşağıdakine benzer bir görüntüye sahip olacağını söyleyebiliriz.

4

TPT Tasarımlı Veritabanını EF ile Kullanmak

Projeye bir ADO.NET Entity Data Model eklendikten sonra oluşturulan Entity sınıflarının ilk görüntüsü aşağıdaki gibi olacaktır.

5

Mevcut EDMX’i analiz etmek gerekirse:

  • SQL’deki her tabloya karşılık bir sınıf oluşturulmuştur.
  • Urun tablosuyla, DVD ve Kitap tabloları arasında bulunan 1’e 1 ilişkisi, sınıflar üzerine de uygulanmıştır.
  • Bir satırın, diğer tabloda bulunan ilişkisel satırına ulaşmak için Navigation Property’ler oluşturulmuştur.

Ancak şunu söylemek gerekir ki, yukarıda, diagramı görülen sınıflar, doğru bir veri modelini kesinlikle ve kesinlikle ifade etmemektedirler! Diğer bir deyişle, sunucudaki veritabanıyla, uygulamanın iş katmanı arasında doğru bir köprü kurulamamaktadır. Bunun en büyük sebebi aslında şudur : Veritabanındaki Urun ismindeki tablo, kolon tekrarını önlemek ve ortak kolonları tek bir tabloda toplamak üzere oluşturulmuştur, aslında üretilen yazılım sisteminde Urun nesneleri olmayacaktır. Kısaca, iş katmanı içerisinde sadece DVD ve Kitap nesneleri oluşmalı, Urun ise soyut bir sınıf olarak tasarlanmalıdır. Hmm, anlaşılan veri modelinin şu anki hali, doğru bir OOP ( Nesne Yönelimli Programlama) tasarımı değildir.

Peki modelin bu haliyle bırakılmasının ne gibi dezavantajları olabilir? Bunu görmek için aşağıdaki kullanımları incelemek yeterlidir :

//20001 ID'li Urunun Bilgilerini Yazdırmak
Urun anaBilgiler = db.Urun.FirstOrDefault(u => u.UrunID == 20001);
if (anaBilgiler != null)
{
    Kitap detaylar = anaBilgiler.Kitap;

    Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", anaBilgiler.UrunID.ToString(),
        anaBilgiler.Ad,
        detaylar.Yazar);
}

//Yeni bir DVD eklemek
Urun anaBilgiler = new Urun
{
    UrunID=10003,
    Ad="Yüzüklerin Efendisi",
    Fiyat=30,
    KdvOran=0.18
};
anaBilgiler.DVD = new DVD
{
    Yonetmen="Peter Jackson",
    Sure=TimeSpan.FromMinutes(167),
    Tur="Fantastik"
};

db.AddToUrun(anaBilgiler);
db.SaveChanges();

Görüldüğü üzere, modelin bu şekli ile Entity’lerin yüklenmeleri ve CRUD operasyonlarındaki kullanımları oldukça zahmetlidir. Bu sebeple aşağıdaki işlemlerin yapılması ve veri modelinin doğru bir şekilde kurulması oldukça önemlidir.

  1. Sınıflar arasındaki 1’e N ilişkileri silelim.

    6
  2. Kitap ve Urun sınıflarından UrunID özelliklerini silelim. Ayrıca bu sınıfları Urun sınıfından kalıtalım. Bunun için ToolBox’tan Inheritance aracını kullanabiliriz.
  3. Urun sınıfının özelliklerine giderek bu sınıfı Abstract olarak belirtelim.
  4. Son olarak Mapping Details penceresinden hem Kitap hem de DVD sınıfları için, UrunID kolonlarını, kalıtım yoluyla Urun sınıfından aldıkları UrunID özelliğiyle eşleyelim.

7

Yapılan bu düzenlemeler neticesinde, doğru bir Entity Data Model oluşturulduğunu söyleyebiliriz. Bu noktada OOP çerçevesinde ilerlendiğinin de altını çizmekte fayda vardır. Özellikle Urun sınıfının Abstract olmasıyla, sadece Urun tablosuna kayıtları girip detayları atlamak gibi olası hataların önüne geçilmiştir. Ayrıca hem Kitap hem DVD sınıflarında olması istenen ortak üyeler,  Inheritance ilişkisi sayesinde bu iki sınıfın ortak taban sınıfı olan Urun’de tanımlanabilirler. Gelelim yapılan düzenlemeler sonucu kodlamanın nasıl olması gerektiğine.

//20001 ID'li Urunun Bilgilerini Yazdırmak
Kitap aranan = db.Urun.OfType<Kitap>().FirstOrDefault(k => k.UrunID == 20001);
if (aranan != null)
{
    Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", aranan.UrunID.ToString(),
        aranan.Ad,
        aranan.Yazar);
}

//Yeni bir DVD eklemek
DVD yeni = new DVD
{
     UrunID = 10004,
     Ad="Jurassic Park",
     Fiyat=20,
     KdvOran=0.18,
     Sure=TimeSpan.FromMinutes(126),
     Yonetmen="Steven Spielberg",
     Tur="Bilim Kurgu"
};

db.AddToUrun(yeni);
db.SaveChanges();

//Tüm kitapları listelemek
foreach (Kitap ktp in db.Urun.OfType<Kitap>())

     Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", ktp.UrunID.ToString(),
ktp.Ad,
ktp.Yazar);
}

TPH Tasarımlı Veritabanını EF ile Kullanmak

İkinci veri kaynağı için bir model oluşturulduğunda, öncelikle aşağıdaki gibi tek bir sınıfın üretildiği görülür. Tabi ki, bu veri kaynağında tüm bilgiler tek tabloda bulunduğundan ötürü, varsayılan olarak modelde tek bir sınıf üretilir.

8

Peki bu durumda modelin kullanarak verilere ulaşımı nasıl gerçekleşecektir? Aşağıda, mevcut EDMX ile verilere nasıl erişileceği görülebilir.

//20001 ID'li Urunun Bilgilerini Yazdırmak
Urun aranan = db.Urun.FirstOrDefault(k => k.UrunID == 20001);
if (aranan != null)
{
    Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", aranan.UrunID.ToString(),
        aranan.Ad,
        aranan.Yazar);
}
//Yeni bir DVD eklemek
Urun yeni = new Urun
{
    UrunID=10003,
    Ad="Yüzüklerin Efendisi",
    Fiyat=30,
    KdvOran=0.18,
    Sure=TimeSpan.FromMinutes(167),
    Yonetmen="Peter Jackson",
    Tur="Fantastik",
    UrunTip="D"
};

db.AddToUrun(yeni);
db.SaveChanges();

//Tüm kitapları listelemek
foreach (Urun urn in db.Urun)
{
     if (urn.UrunTip == "K")
     {
         Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", urn.UrunID.ToString(),
urn.Ad,
urn.Yazar);
     }
}

Hmm, pek iç açıcı görünmediğini itiraf etmeliyimSmile Özellikle UrunTip özelliğinin bu kullanımı hata riskini de arttırmaktadır. Halbuki, birinci veri kaynağı için oluşturulan veri modelerinin son şekli, gerçekten de etkili bir veri erişimi sağlamaktaydı.

Aşağıdaki adımlar izlenerek, ilk modeldekine benzer bir kalıtım hiyerarşisi oluşturulabilir.

  1. Modelde sağ tuşla boş bir yere tıklayarak açılan menüden Add Entity diyelim. Sonrasında Entity Name alanına Kitap yazarak, Base Type olarak Urun tipini belirtelim.
  2. Yazar,SayfaSayi ve BasimYil özelliklerini Urun sınıfından keserek Kitap sınıfına yapıştıralım.
  3. Yonetmen,Sure ve Tur özelliklerini ise Urun sınıfından keserek DVD sınıfına yapıştıralım.

    9
  4. DVD sınıfını Mapping Details penceresinden Urun tablosuyla eşleştirelim. Ayrıca Add Condition kısmından UrunTip değeri “D” olan satırların DVD nesneleriyle eşleştirileceğini belirtelim.

    10 
  5. Kitap sınıfını Mapping Details penceresinden Urun tablosuyla eşleştirelim. Ayrıca Add Condition kısmından UrunTip değeri “K” olan satırların Kitap nesneleriyle eşleştirileceğini belirtelim.
  6. Urun sınıfını, özellikler penceresinden Abstract olarak değiştirelim ve UrunTip özelliğini bu sınıftan kaldıralım.

Modelin son şekli aşağıdaki gibi olmalıdır :

11

Bingo!!! Kullanılan her iki veritabanı da birbirinden çok farklı olmasına rağmen aynı sonuca ulaşıldı. Tabi ki veri kaynaklarının farklı yapıları gereği farklı adımlar izlendi; fakat neticesinde varılan nokta her ikisinde de birebir aynıdır. Peki, eğer veri modellerinde varılan noktalar aynıysa, veri erişimi sırasında kullanılan kodlar da aynı olacak mıdır? Kocaman bir EVETWinkAşağıdaki örnek kullanımları bir yerden hatırlıyor olmalısınız. Doğru, TPT örneğindeki kodların aynılarıSmile

//20001 ID'li Urunun Bilgilerini Yazdırmak
Kitap aranan = db.Urun.OfType<Kitap>().FirstOrDefault(k => k.UrunID == 20001);
if (aranan != null)
{
    Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", aranan.UrunID.ToString(),
        aranan.Ad,
        aranan.Yazar);
}

//Yeni bir DVD eklemek
DVD yeni = new DVD
{
     UrunID = 10004,
     Ad="Jurassic Park",
     Fiyat=20,
     KdvOran=0.18,
     Sure=TimeSpan.FromMinutes(126),
     Yonetmen="Steven Spielberg",
     Tur="Bilim Kurgu"
};

db.AddToUrun(yeni);
db.SaveChanges();

//Tüm kitapları listelemek
foreach (Kitap ktp in db.Urun.OfType<Kitap>())

     Console.WriteLine("Kitap ID : {0}\nAd : {1}\nYazar : {2}", ktp.UrunID.ToString(),
ktp.Ad,
ktp.Yazar);
}

Örneklerde de görüldüğü gibi, oluşturulan veri modelleri sayesinde her iki veri kaynağı da aynı kodlarla kullanabilmektedir. Diğer bir deyişle, veritabanı tasarımları farklı olsalar da, kullanılan veri modellerinin programcıyı veritabanı tasarımından soyutlaması sonucu, aynı kodlama biçimiyle veri erişimi sağlanabilmektedir. Bu da günümüz programlama ihtiyaçları gözönüne alındığında gerçekten önemli bir avantajdır. Çünkü uygulamanın kodları yazıldıktan sonra tablolar üzerinde yapılan bu tarz tasarımsal değişikliklerde, yalnızca model üzerinde düzenlemeler yapmak yeterli olacak, uygulamanın kod kısmı üzerinde ekstra bir çalışmaya gerek kalmayacaktır.

Faydalı olması dileğiyle…