19 Mayıs 2012 Cumartesi

Entity Framework - Database First

Merhaba arkadaşlar.
"EntityFramework 4.0 ile gelen ORM araçlarıyla : db yada uygulama model den birini oluştururuz, diğeri ondan oluşur. Varolan bir database i uygulamaya uyarlama işine Database First yaklaşımı, uygulamada var olan nesneleri de database e uyarlama işlemine de Model First yaklaşımı denir."
demiştik ORM giriş yazımızda. Bu yazımda "Database First" yaklaşımını inceleyeceğiz. Database deki her bir tablo bir entity sınıfı, o tablonun her bir kolonu ise bir porperty olacaktır. Entity nin kelime anlamı ise birim yada varlıktır. Primary key ler entity key olacaktır. Bu kolonların tipini ve tablolardaki foreign key olarak tanıladığımız, tablonun diğer tablolar ile arasında olan ilişkiyi nasıl göstereceğimizi göreceğiz. Gelin o zaman Northwind veritabnını map edebilim. Bunun için SqlServer daki diagramımıza bir bakalım.

Projeme Data sekmesi altında, bir ADO.NET Empty Data Model ekliyorum. Adına Nortwind veritabanını modelleyeceğimden dolayı Northwind diyorum.
İki seçenek geldi karşıma. Generate form Database, Empty Model. Ben varolan bir database den türeteceğim, generate edeceğim. Bu nedenle Genarte from Database seçeneğini seçiyorum. Eğer Model First çalışsaydım Empty Model seçeneğini seçecektim. Next dedim.
Aşağıdaki pencerede amaç connection string imizi oluşturmak. Önceden oluşturduysak görüldüğü gibi ismiyle birlikte saklar ve bu seçeneği sunar. Farklı bir connection string ile bağlanacaksam new connection diyerek belirtebilirim. Ben windows authentication ile bağlanacağım. Fakat sql  authentication ile giriş yapacaksam olsaydım sensitive dataları saklama seçenekleri enable oluırdu. Sensitive datalardan kastı user id ve password bilgileridir. Altında app.config dosyasında hangi isimle görüneceğini belirtiyor. Değiştirebiliriz ama kalsın değiştirmiyorum. Bu arada Entity Connection String 'deki EntityClient Provider arka planda SqlClient kullanır. Zaten new connection desem de değiştirilemeyeceğini görebilirsiniz. Bu da ORM 'nin sadece sql server uyumlu olduğunu gösterir. DotNet 4.1 den sonra entity ile oracle a bağlanılabilir. 
Veritabanındaki tablo, view, sp nesnelerim geldi. SqlServer daki tüm nesnelerimin uygulamamda nasıl generate edildiğini görmek açısından tüm nesnelerimi seçiyorum.

Plurize or singularize generated object names seçeneği, sadece ingilizce tablo isimleri için kullanılır. Bu seçeneği tikleyerek, çoğul tablo isimleri entity e dönüştüğünde tekil isimli olacaktır. Bu seçeneği tikliyorum. Her tablo (ara tablo değilse) entitydir. Include forgn key columns in the model seçeneğiyle, tablodaki her bir foregin key için de birer property oluşturacaktır. Bu seçeneği de tikliyorum. Finish diyorum.              

Görüldüğü üzere aşağıdaki gibi Solution Explorer penceresine edmx(Entity Data Model) uzantılı Northwind Data Model 'im geldi. Referanslara da System.Data.Entity, System.Runtime.Serialization, System.Security dll 'lerini kendisi ekledi.
App.config dosyasına da connection stringi belirttiğim isimde yazdı.

  <connectionStrings>
    <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=Northwind;integrated security=True;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>

Aşağıda edmx dosyasının bir kısmını görüyorsunuz. Görüldüğü üzere her bir tablo entity sınıfı, her bir kolon property olmuş. İşte arkadaşlar Mapping budur. Entity lerin ikonu, class ikonu olan mavi küp şeklinde. Sql server daki tablolara baktığımızda arasında one to many, one to one, many to many ilişkileri mevcuttu. Sql server diagramlarından farklı olarak buradaki ilişkilerde many to many ilişkiler için oluşturulan ara tabloları göremeyiz. Örneğin Customer classının Orders adında bir navigation property si var. Eğer bu iki tabl many to many ilişkili olsaydı, Order tablosunda da Customer değil Customers adında bir navigation property si bulunurdu.
Bir değer bir farklılık ise aşağıdaki gibi one or zero (0,1) to many ilişkisi. one or zero (0,1) to one da olabilirdi. Bu ilişkinin okunmasınsa dikkat edilmesi gereken nokta ya bir müşterinin Order tablosunda birden fazla kaydı bulunabilir. Yada bir müşterini order tablosunda hiç kaydı olmayabilir, şeklinde okunur. Bunun yapılabilmesi için Order tablosundaki CustomerID foreign key kolonu nullable (boş geçilebilir) bir alan olmalıdır. Sql Server a bakalım gerçekten de öyle mi?
Görüldüğü üzere boş geçilebilir bir alan. Modellenirken de bu ilişkiyi 0,1 bir şeklinde belirleniyor.

Bu modellemenin en güzel özelliklerinden biri de yukarıda da göründüğü gibi entitylerin altında bulunan Navigation Properties 'dir. Örneğin Order entity si için bu ilişki şöyle okunur: Her order ın customer i, employee si, details leri, shipper i vardır. Yani bildiğimiz OOP deki Has A kavramı. Bir sınıfta başka bir sınıfın tipinde bir sınıf üyesi bulunabilir. Burada da bir entity de başka bir entity tipinde bir entity üyesi bulunabilir. Order_Details navigation property sinin çoğul olmasının nedeni, bir orderın birden fazla detail inin olabileceğindendir. Entity, navigation property si içinde bulunan entity ile ilişkisine göre, navigation property i tekil yada çoğuldur. Navigation property lerini kullanarak t-sql de inner join ile yapabildiğimiz her şeyi "Order  o = new Order(); o.Customer.CompanyName" diyerek erişebilmemizdir. Örneğin :
Gerçekten büyük kolaylık. Aynı işlemi ado.net ile gerçeklemeye çalışsaydık, inner join li bir sorguyu SqlDataReader nesnesiyle çalıştırmak zorunda kalacaktık. Kısacası navigate prop, bir entity nin aynı model içerisindeki bir başka entity nin özellikleri dir.

Madem bir mapping yani bir modelleme işlemi yapıyoruz, SqlServer daki her bir tipin uygulamada bir karşılığı olmalıdır. Örneğin her bir kolon tipinin nasıl bir karşılıklarının olduğunu görmek için, şimdi de bu property lerin özelliklerine bir bakalım.
 




CustomerName property sinin tipi stringtir. Sql de nchar, nvarchar, char, varchar tipleridir. unicode= true ise nchar yada nvarchar dır, Fixed lengt=true ise nvarchardır. Max length=5 ise nvarchar(5)dir. Bu bilgileri birleştirerek sql server da nvarchar(50) oduğunu, nullable=false ile de boş geçilemez olduğunu anlıyoruz. Bakalım SqlServer da gerçekten de öyle mi?


Bir de OrderID property sine bir bakalım.
Tipi "int32", "StoreGenaratedPattern = identity" yani otomatik artan, "entity key=true" olması sql server da  "primary key" olduğunu gösterir. "Nullable=false" ile de boş geçilemez olduğunu anlıyoruz.

Örneğin "order entity" sindeki "freight property" sinin tipi "decimal" olmuştur. Çünkü sql de "money" olan tiplerin karşılığı "decimaldir". "Precision ve Scale" özellikleri "Decimal(19,4)" olduğunu belirtir.
Görüldüğü üzere her tipin bir karşılığı bulunur.

"Northwind.Designer.cs" dosyası "ORM" mizin nasıl bir "Code Generating" işlemini yaptığını göreceğimiz dosyadır. İncelemek gerekirse:
  • Constructors: Bu region altında "NorthwindEntities" context class ımız hangi yapıcı ile örneklendiğinde, base sınıfı yani "ObjectContext" sınıfı hangi yapıcısıyla örnekleneceği tanımlanmış. DotNet 4.1 de "CodeFirst" yaklaşımını uygularken bu context i, "DbContext" sınıfından türeteceğiz.
  • ObjectSet Properties: Bu region altın "ObjectSet" generic koloksiyonu her enitity tipi için tanımlamış.
  • AddToMethods: ObjectSet Properties 'deki ObjectSet generic koloksiyonlarına ekleme metotları tanımlanmış.
Şimdi de Entities ragion altında ne gibi yapılar meydana geldiğine bir bakalım. 
  • Entities: Bu bölümde ise veritabanındaki her bir tablomun ve view nesnelerinin partial class 'a dönüştürüldüğünü görebiliriz. Yukarıdaki resimde üstteki view, alttaki tablodur. View in de bir tablo gibi generate edilmesinin sebebi geriye tablo gibi bir sonuç döndürmesidir. Partial class olmasının nedeni ise örneğin Shipper classına bir şey eklemek istediğimde, benim yaptığım bu ekleme işlemlerini bu NorthwindEntities classıyla karışmaması açısından fiziksel olarak ayrı, fakat aynı isimde, yine bir partial bir classda tanımlamak isteyebilirim. Bu nedenle partial tanımlanmıştır. Biraz sonra Shipper classı için ToString metotunu override etme ihtiyacı duyacağız. Bunun için aşağıdaki gibi bir "OverrideToStrings" classı oluşturuyorum. İsmini sadece dosya ismi için verdim. Amacım tüm entity lerin "ToString" metotunu bu dosyada ezmek.
    public partial class Shipper    
    {
        public override string ToString()
        {
            return this.CompanyName;
        }
    }

Burada oluşturduğum "Shipper" sınıfının "access modifier" ı, "NorthwindEntities" classındaki "Shipper" class ıyla aynı olmak zorundadır. Aksi halde derleme esnasında bu parital class lar birleştiğinde bir çakışma meydana gelir ve hata ile karşılaşılır. Ama oluşturduğumuz sınıfın "access modifier" ını belirtmezsem hata almam, derleme esnasında diğer partial sınıfın "access modifier" ın alır.

Şimdi Entities region ı altındaki ragion lara da kısaca değinelim.
  • Factory Method: Bu reigon da içinde bulunduğu entity den bir örnek oluşturuluyor.
  • Primitive Properties: Yukarıdaki mapping görüntüsünde entitylerdeki properties sekmesi altındaki propertylere "Primative Properties" denir. "Entities" region altında her entity için "Primitive Properties" region mevcut. Bu region da içinde bulunduğu entity nin propertylerinin (db deki kolonlar) "attiribute" larla özellikleri belirtilmiş. Get ve set metotları oluşturulmuş.
  • Navigation Properties:  Bu ragion da yukarıdaki mapping görüntüsünde entity lerdeki "Navigation properties" leri generate edilmiştir. Primative proplerdan farklı attirbute ler ile nitelenmiştir.
Bir de ObjectContext sınıfındaki bazı metotlara bir bakalım:
Görüldüğü üzere AddObjeect, SaveChanges, DeleteObject, DeleteObject, CreateDatabase, DeleteDatabase verilerimi maüpüle edebileceğim bir çok metotun hazır olarak gelmiş.

Şimdi kodlara geçelim. Aşağıdaki gibi bir arayüz hazırlayalım.
Gerisi bizim bildiğimiz klasik OOP tekniğidir. Insert ve Update işlemlerini aynı "Button" altında tasarlamayı tercih ettim. Siz istediğiniz gibi kullanabilirsiniz. NorthwindEntities nesnesin bize sağladığı metotları ve koleksiyonları kullanarak verilerimizi manipüle edebileceğiz. 

        NorthwindEntities entities = new NorthwindEntities();       //object context
        private void buttonKaydet_Click(object sender, EventArgs e)
        {
            if (selectedShipper == null)
            {
                selectedShipper = new Shipper();
                entities.Shippers.AddObject(selectedShipper);      //remde gerçekleşti.
            }
            selectedShipper.CompanyName = textBoxFirma.Text;
            selectedShipper.Phone = textBoxTel.Text;
            entities.SaveChanges();                     //db e kaydedilir. Db ye modelledi

            Clear();
            listBox1.DataSource = entities.Shippers.ToList();       //databaseden sorgular getirir. ToList siz kullanılırsa contextin remdeki son halini getirir. ToList i eklediğimde db den güncel çeker
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            listBox1.DataSource = entities.Shippers.ToList();            
        }

        public void Clear()
        {
            selectedShipper = null;
            textBoxTel.Text = textBoxFirma.Text = string.Empty;
            textBoxFirma.Focus();
        }

        private Shipper selectedShipper;
        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (listBox1.SelectedItem != null)
            {
                selectedShipper = listBox1.SelectedItem as Shipper;
                textBoxFirma.Text = selectedShipper.CompanyName;
                textBoxTel.Text = selectedShipper.Phone;
            }
        }

        private void buttonYeni_Click(object sender, EventArgs e)
        {
            Clear();
        }

        private void buttonSil_Click(object sender, EventArgs e)
        {
            if (selectedShipper != null)
            {
                entities.Shippers.DeleteObject(selectedShipper);
                entities.SaveChanges();
                Clear();
            }
        }
Umarım faydalı olabilmişimdir. Bir başka yazıda görüşmek dileğiyle, hoşçakalın...

3 yorum:

  1. Paylaşımlarınıza ekip olarak destek çıkıyoruz ve begenmeye Sürmeboru olarak devam edecek.

    YanıtlaSil
  2. sanırım biraz daha anlamaya başladım. teşekkür ederim.

    YanıtlaSil
  3. Entity Framework teknolojisi, ORM yapısı sayesinde özellikle dead line 'i kısa olan projeler için tabi ki biçilmiş kaftan. Microsof 'un da hala veriye erişim teknolojisi kategorisinde gözbebeği durumunda ve ciddi arge yatırımına sahip. Fakat ne kadar gelişmiş bir teknoloji olursa olsun sizin profosyonelce kendi hazırladığınız bir yapının yerini tutamaz. Çünkü tüm hazır yapıların genel dezavantajı olan "bağımlılık" sözkonusu. Sizin istediğiniz kadar esneklik gösteremeyebilir. Bu nedenledir ki benim size nacizane tavsiyem kendi katmanlarınızı oluşturmanız. Temel olarak Entity (varlıklarınızı belirlediğiniz) ve DAL (Data Access Layer-Varlıklarınızı manipule ettiğiniz) iki katman yeterli olacaktır. DAL katmanınızda bir SqlHelper (DB'ye bağlantı yardımcısı) class 'ı yardımıyla ciddi oranda kod kısaltması yapabilirsiniz. Hatta ve hatta repository pattern yardımıyla herhangi bir Service katmanı tasarlayarak Surrugate Type (Taklitçi Tipler: Diğer adı DTO-Data Transfer Objects) larınızı kullanarak arayüzünüze varlıklarınızın tüm property 'lerini değil de sadece kullanıcıya gerekli olan özelliklerini getirerek ciddi bir performans kazanabilirsiniz. Hele ki proje tipiniz bir web uygulamasıysa SEO (Search Engine Optimization) bakımından da bir adım önde olacaksınız. Ne kadar SQL Server 2012 ile birlikte Column Stored Index (Kolona Göre Indexleme) özelliği gelse de uygulamadaki POCO (Plain Old CLR Objects) class 'larınız için durum hala aynı olmakta.

    Konuyu dallandırmadan şu söylenebilir :
    Hiç bir hazır yapı kendi hazırladığınız katmanlar kadar esnek yani geliştirilebilir olamaz.

    Yorumun için teşekkürler, görüşmek dileğiyle..

    YanıtlaSil