Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

Entity Framework - POCO ve Lazy Loading

Pazartesi, 10 Mayıs 2010 09:55 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız üzere bir önceki yazımızda Ado.Net Entity Framework içerisinde POCO(Plain Old CLR Object) nesnelerinin kullanımını incelemeye çalışmıştık. Örneğimizde kullanmış olduğumuz LINQ sorgusu basit bir Join işlemini gerçekleştirmekteydi. Tabi Join sorgusu kullandığımız için gözden kaçırdığımız ufak ama bir o kadar da önemli bir vaka oluşmaktadır. Bu vakayı ele almak için program kodunu biraz daha değiştirdiğimizi ve aşağıdaki hale getirdiğimizi düşünelim.

using System;
using System.Linq;
using ChinookModel;

namespace POCODans
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Sample 2

                // Şirket bilgisi null veya boş olmayan müşteriler
                var customers = from c in entities.Customers
                                where !String.IsNullOrEmpty(c.Company)
                                select c;

                foreach (var customer in customers)
                {
                    Console.WriteLine("Company : {0} City : {1}",customer.Company,customer.City);

                    // ve bu müşterilere ait fatura bilgileri
                    foreach (var invoice in customer.Invoices)
                    {
                        Console.WriteLine("\tInvoice Date : {0} ,Total : {1}",invoice.InvoiceDate,invoice.Total.ToString("C2"));
                    }
                }

                #endregion
            }
        }
    }
}

Örneğimizin bu yeni haline göre Company alanı dolu olan(Null veya Empty olmayan) müşterilerin şirket adları ile bulundukları şehir bilgileri, ayrıca bu firmaların faturalarına ait tarih ve tutarları ekrana yazdırılmaktadır. Bu kodun çalışması sırasındaki beklentimiz ise Customer ve bunlara bağlı olan Invoice bilgilerinin getirilmesidir. Ancak uygulamayı çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ettiğimizi görürüz. Undecided

Dikkat edileceği üzere sadece Customer bilgileri çekilebilmiş ancak iç foreach döngüsü tarafından o anki Customer nesnesine ait Invoice bilgileri yazdırılmamıştır. Aslında bunun sebebini anlamak için SQL Server Profiler aracı yardımıyla arka planda çalıştırılan SQL sorgularına bakmamız yeterli olacaktır. Nitekim aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaştığımızı görürüz.

Dikkat edileceği üzere kodun çalışması sonrasında sadece Customer bilgilerinin çekildiği görülmektedir. Tabi bu noktada örneğimizin POCO nesnelerini kullandığını unutmayalım. Eğer örneğimize ait Entity Model' in otomatik olarak üretildiğini düşünürsek aynı kodun çalışma zamanında aşağıdaki sonuçları ürettiğini görebiliriz.

Dikkat edileceği üzere fatura bilgileri ekrana yazdırılmaktadır. Tabi SQL Server Profiler aracının üzerinden çalıştırılan SQL sorgularına baktığımızda aşağıdaki ifadelerin yer aldığını da görebiliriz.

Dikkat edileceği üzere kaç tane Customer bilgisi çekilmişse her biri için Invoice tablosundan bilgi çekmek amacıyla birer SQL sorgusu icra edilmiştir. Çok doğal olarak burada Ado.Net Entity Framework' ün 4.0 versiyonu ile birlikte otomatik olarak kazandığı Lazy Loading kabiliyetinin devreye girdiğini söyleyebiliriz. Peki POCO nesneleri için Lazy Loading işlevselliğini nasıl kazandırabiliriz? Burada biraz dikkali olunması gerekmektedir. Nitekim Lazy Loading yeteneği için yapılacak eklentiler, POCO nesnelerinden Framework' e doğru bir bağımlılık oluşturmamalıdır. Normal şartlarda Lazy Loading işlemi için akla gelen ilk yöntem Customer tipi içerisinde yer alan Invoices özelliğinin get bloğunda gerekli kodlamaları yapmaktır. Oysaki bu hamle sonucunda Framework' e bir bağımlılık oluşturulması kaçınılmazdır ki POCO nesnelerinin ilkesine aykırı bir durumdur. Bu nedenle dinamik olarak üretilen proxy tiplerinden(Dynamically Generated Proxies) yararlanılmaktadır. Bu proxy tipleri aslında POCO nesnesinden türeyen ve ilgili özellikleri override ederek Lazy Loading gibi yetenekleri içeriye enjekte eden sınıflar olarak düşünülebilir. Aslında Ado.Net Team Blog' da bu tip Proxy sınıflarının nasıl geliştirildiği ve kullanıldığından bahsedilmektedir. Ancak biz çok daha basit bir yol izliyor olacağız.

POCO nesnelerinde Lazy Loading işlemini gerçekleştirebilmemiz için üç basit kurala dikkat etmemiz yeterlidir. Buna göre Context tipine ait yapıcı metod(Constructor) içerisinde gerekli bazı üst özelliklerin etkinleştirilmesi, Navigation Property' nin virtual olarak tanımlanması ve POCO tipinin public ve sealed olmaması yeterlidir. Örneğimizin kod içeriğinin yeni hali aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.Data.Objects;

namespace ChinookModel
{
    public class ChinookEntities
        :ObjectContext
    {
        private ObjectSet<Customer> _customers;
        private ObjectSet<Invoice> _invoices;
       
        public ChinookEntities()
            :base("name=ChinookEntities")
        {
            // KURAL 1:
            // Dinamik proxy üretimi etkinleştirilir. Varsayılan değeri true dur
            this.ContextOptions.ProxyCreationEnabled = true;
            // Çalışma zamanında Navigation Property' lere erişildiğinde Lazy Loading işleminin otomatik olarak gerçekleştirilmesi için true değeri atanır.
            this.ContextOptions.LazyLoadingEnabled = true;
        }

        public ObjectSet<Customer> Customers
        {
            get
            {
                if (_customers == null)
                {
                    _customers = base.CreateObjectSet<Customer>();
                }
                return _customers;
            }
        }

        public ObjectSet<Invoice> Invoices
        {
            get
            {
                if (_invoices == null)
                {
                    _invoices = base.CreateObjectSet<Invoice>();
                }
                return _invoices;
            }
        }
    }

    // KURAL 2 : POCO sınıfı public olmalı ve sealed olarak işaretlenmemelidir.
    public class Customer
    {
        public int CustomerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Email { get; set; }

        private List<Invoice> _invoices = new List<Invoice>();

        // KURAL 3 : Navigation Property özelliğinin virtual olması gerekir.
        public virtual List<Invoice> Invoices
        {
            get { return _invoices; }
            set { _invoices = value; }
        }
    }

    public class Invoice
    {
        public int InvoiceId { get; set; }
        public int CustomerId { get; set; }
        public DateTime InvoiceDate{ get; set; }
        public string BillingCity { get; set; }
        public string BillingCountry { get; set; }
        public decimal Total { get; set; }
        public Customer Customer { get; set; } 
    }
}

Ve örneğimizin çalışma zamanı hali;

Volaaaa!!! Laughing Üstelik SQL Server Profiler aracında da tam istediğimiz şekilde sorguların çalıştırıldığını görebiliriz.

Tabi buradaki kuralların neden var olduğuna dikkat edilmelidir. Söz gelimi LazyLoadingEnabled, ProxyCreationEnabled değerlerinin true olması çalışma zamanındaki Entity motoru için gereklidir. Bu değerlerin false olması halinde bir hata mesajı alınmaz ancak istenen çalışma zamanı çıktıları da elde edilmez. Diğer taraftan POCO tipinin sealed olması halinde zaten virtual eleman içermesi ile ilişkili olarak derleme zamanı hatası alınacaktır.

Buna ek olarak derleme zamanı POCO nesnesinin public olmasını beklemektedir. Public olarak tanımlanmadığı takdirde yine bir hata mesajı üretilecektir.

Navigation Property' nin virtual olarak tanımlanmaması derleme veya çalışma zamanında bir hata mesajı üretmezken Lazy Loading işleminin de çalışmamasına neden olmaktadır. İşte bu kadar. Bu yazımızda POCO nesnelerinin kullanıldığı vakalarda Lazy Loading işlemlerinin nasıl ektinleştirilebileceğini incelemeye çalıştık. Unuttuğumuz bir şey var mı? Aslında olabilir. Wink Eğer öyleyse de ilerleyen yazılarımızda ele almaya çalışacağız. Bir sonraki yazımızda görüşünceye dek hepinize mutlu günler dilerim.

POCOandLazyLoading_RTM.rar (90,73 kb) [Örnek uygulama Visual Studio 2010 Ultimate RTM sürümü üzerinde geliştirilmiş test edilmiştir]

Entity Framework - POCO(Plain Old CLR Objects)

Cumartesi, 1 Mayıs 2010 00:01 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resimde Seychelles Blue Pigeon olarak adlandırılan ve Hint Okyanusundaki 115 adadan birisi olan Republic of Seychelles kolonisine has bir güvercin resmi yer almaktadır. Aslında bu kuş oldukça meşhurdur. Nitekim çeşitli hayvan türlerini genellikle kitap kapaklarında kullanan O'Reilly yayınlarının uzun zaman önce çıkarttığı ve Julia Lerman tarafından yazılmış olan Programming Entity Framework baskısında bu kuşa yer verilmiştir. Kitaptan bahsetmişken...Bendeki baskısı Ado.Net Entity Framework 3.5  sürümünü içermekteydi. Doğal olarak köprünün altından çok sular geçti ve artık 4.0 sürümü ile karşı karşıyayız. Kitabın Aralık 2009 baskısında 4.0 vesiyonu içinde ek bilgiler yer almakta. Ancak sanıyorum ki yakın zamanda son sürümüne kavuşacak olan .Net Framework 4.0 ile birlikte yeni bir baskısı daha çıkacaktır.

Aslında Ado.Net Entity Framework 4.0 tarafında yer alan önemli kavramlardan birisi de POCO nesnelerinin kullanımı. Ado.Net Entity Framework 3.5(Service Pack 1) sürümünde net bir şekilde ele alınmayan POCO nesneleri için, 4.0 versiyonunda tam destek söz konusu. POCO(Plain Old CLR Objects), .Net Framework üzerinde herhangibir bağımlılığı olmadan tanımlanabilen nesneler olarak düşünülebilir. POCO nesneleri herhangibir sınıftan türemez(Class Inheritance), çeşitli arayüzleri uygulamaz(Interface Implementation) veya özel nitelikler(Attributes) ile işaretlenmezler. Sadece verinin üzerlerinden akmasını sağlayan hafif siklet(Lightweight) nesnelerdir. Oysaki Entity Framework tarafında üretilen tipler düşünüldüğünde, türetmelerin, nitelik işaretlemelerinin ve IPOCO olarak adlandırabileceğimiz bazı arayüz uyarlamalarının yapıldığı görülür. Bu nedenle Entity nesnelerinin, Persistence kıstasını göz ardı etmesi veya test senaryoları düşünülerek Entity Framework yapısı içerisinde ele alınmaları zorlaşmaktadır. Dolayısıyla Entity Framework tarafına getirilen POCO desteğinin önemi büyüktür.

Visual Studio 2010 ortamında geliştirilen Ado.Net Entity Framework bazlı uygulamalarda POCO nesnelerinin oluşturulması ve kullanımı son derece kolaydır. İşte bu yazımızda POCO nesnelerinin ne işe yaradığını, nasıl tanımlandıklarını ve kullanıldıklarını incelemeye çalışıyor olacağız. İşe başlamadan önce POCO' suz bir hayatın nasıl olacağına bakmamızda yarar olduğu kanısındayım. Bu amaçla örnek bir Console uygulamasını Visual Studio 2010 Ultimate RC sürümü üzerinden geliştirerek devam edebiliriz. Örneğimizde Chinook veritabanını baz alan ve aşağıdaki Entity Model diagramında görülen tiplere sahip olduğumuzu düşünelim.

Çok basit anlamda Customer ve Invoice tablolarına karşılık gelen Entitiy tipleri söz konusudur. Bu tipler arasında bire çok(one-to-many) ilişki mevcuttur. Entity Model tarafında üretilen sınıf kodlarına baktığımızda aşağıdaki içeriklerin oluşturulduğunu görürüz.

Customer sınıfı;

[EdmEntityTypeAttribute(NamespaceName="ChinookModel", Name="Customer")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Customer : EntityObject
{

Invoice sınıfı;

[EdmEntityTypeAttribute(NamespaceName="ChinookModel", Name="Invoice")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Invoice : EntityObject
{

Aslında her iki Entity sınıfı içinde dikkat edilmesi gereken nokta, EntityObject tipinden türemeleri ve bazı nitelikler(Attribute) tarafından imzalanmış olmalarıdır. Üstelik sınıflar içerisinde yer alan özelliklere(Properties) de bazı niteliklerin(Attribute) uygulandığı görülür. Örneğin;

        [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
        [DataMemberAttribute()]
        public global::System.Int32 CustomerId
        {
            get
            {
                return _CustomerId;
            }
            set
            {
                if (_CustomerId != value)
                {
                    OnCustomerIdChanging(value);
                    ReportPropertyChanging("CustomerId");
                    _CustomerId = StructuralObject.SetValidValue(value);
                    ReportPropertyChanged("CustomerId");
                    OnCustomerIdChanged();
                }
            }
        }
        private global::System.Int32 _CustomerId;

gibi.

Bu noktada söz konusu tiplerin bazı bağımlıklar taşıdığını düşünebiliriz. Ancak POCO nesnelerinde, yazımızın başında da belirttiğimiz üzere bu tip bağımlılıklar söz konusu değildir. (Buna göre POCO nesneleri için şu tip bir tanımlama da yapılabilir; buradaki Entity tiplerinde yer alan bağımlılıklara ihtiyaç duymayan tiplere olan ihtiyaçlarda göz önüne alınan tiplerdir Wink ) Şimdi örneğimizde ilerlemek için, her iki tipten sadece belirli özellikleri projemizde kullanmak istediğimizi düşünelim. Gerçekten de kod tarafında söz konusu Entity tipleri içerisinde yer alan tüm özelliklere ihtiyacımız olmayabilir. Bu amaçla sembolik olarak, Entity tiplerinden bazı özellikleri(Nullable değeri true olanlar seçilirse iyi olur) silip diagramı aşağıdaki şekilde görülen hale getirelim.

Senaryomuz gereği sadece bu özellikler ile ilgilendiğimiz bir vakamız olduğunu düşünüyoruz. Buna göre Console uygulamamızda çok basit bir LINQ sorgusu yazarak Entity tiplerin kullanılabilir olduğundan emin olmamızda yarar vardır. İşte örnek kod parçamız ve çalışma zamanı çıktısı...

using System;
using System.Linq;

namespace POCODans
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                var results = from c in entities.Customers
                              join i in entities.Invoices on c.CustomerId equals i.CustomerId
                              where c.City=="Sidney"
                              select new
                              {
                                  c.CustomerId,
                                  Name=c.FirstName+" "+c.LastName,
                                  c.Email,
                                  c.Company,
                                  c.City,
                                  c.Country,
                                  i.BillingCity,
                                  i.BillingCountry,
                                  i.InvoiceDate,
                                  i.Total
                              };

                foreach (var r in results)
                {
                    Console.WriteLine(r.ToString());
                }
            }
        }
    }
}

Örnek LINQ sorgusuna göre Customer ve Invoice verileri CustomerId alanı üzerinden birleştirilerek, Sidney' de yaşayan müşteri ve fatura bilgileri isimsiz tip(Anonymous Type) içerisinde toplamakta ve ekrana yazdırmaktadır. Modelimizi test ettiğimizde aşağıdaki sonuçları aldığımızı dolayısıyla çalıştığını görürüz.

Buraya kadar yaptıklarımızı özetlediğimizde, aslında Wizard yardımıyla bir modelin oluşturulduğunu fark edebiliriz. Bu otomatik süreç sonrasında Storage, Conceptual ve Mapping modellerinin üretildiğini ve bunların edmx dosyası içerisine aktarıldığını da anlayabiliriz. Peki bu otomatik üretim hattı göz önüne alındığında, herhangibir bağımlılığı bulunmayan, dolayısıyla otomatik olarak üretilmemesi gereken ve sadece veri aktarımı amacıyla kullanılacak olan bir nesnenin(POCO) entegrasyonunu nasıl gerçekleştirebiliriz?

Öncelikli olarak otomatik kod üretimi seçeneğini pasifleştirmeliyiz. Bu amaçla aşağıdaki şekilden de görüleceği üzere, EDMX' in özelliklerinden Code Generation Strategy değerini None olarak set etmeliyiz.

Bu işlemin sonuçlarını aslında hemen görebiliriz. Uygulamanın build edilmesinin ardından ChinookModel.Designer.cs içeriğine baktığımızda aşağıdaki ekran görüntüsünde yer alan çıktı ile karşılaşırız. Dikkat edileceği üzere normalde var olması gereken tiplerin hiç birisi mevcut değildir.

Şimdi işin en önemli kısımlarından birisine geldik. Entity Model üzerinde görülen Customer ve Invoice tiplerinin POCO versiyonlarını eklemek. Bunun için yapacağımız tek şey birer sınıf oluşturup içerisine gerekli özellikleri koymak olacaktır. Bunu zaten yapmamız gerekmektedir nitekim otomatik üretimi kapattığımız için elimizde artık Customer ve Invoice isimli Entity tipleri de bulunamamaktadır. Wink  Bu amaçla uygulamamıza aşağıdaki sınıfları eklediğimizi düşünelim.

using System;
using System.Collections.Generic;

// Namespace adının ChinookModel olması önemlidir.
namespace ChinookModel
{
    public class Customer
    {
        public int CustomerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Email { get; set; }
        private List<Invoice> _invoices = new List<Invoice>();// Bir Customer' ın birden fazla faturası olabilir

        public List<Invoice> Invoices
        {
            get { return _invoices; }
            set { _invoices = value; }
        }
    }

    public class Invoice
    {
        public int InvoiceId { get; set; }
        public int CustomerId { get; set; }
        public DateTime InvoiceDate{ get; set; }
        public string BillingCity { get; set; }
        public string BillingCountry { get; set; }
        public decimal Total { get; set; }
        public Customer Customer { get; set; } // Birden fazla fatura tek bir Customer ile ilişkilidir.
    }
}

Bir anlamda otomatik olarak üretilen Entitiy sınıflarını yazdığımızı düşünebiliriz ancak çok önemli bir fark bulunmaktadır. Bağımlılıklar. Hiç bir sınıf türetmesi, arayüz uygulaması veya nitelik işaretlemesi mevcut değildir dikkat edeceğiniz üzere. Elbette Customer ve Invoice sınıflarının yazılmış olmaları yeterli değildir. Birde bu tiplerin çalışma zamanındaki yönetimini üstelenebilecek bir içerik(Context) tipi olmalıdır. Bu amaçla uygulamaya aşağıdaki sınıfı eklememiz yeterli olacaktır.

using System;
using System.Collections.Generic;
using System.Data.Objects;

namespace ChinookModel
{
    public class ChinookEntities
        :ObjectContext
    {
        private ObjectSet<Customer> _customers;
        private ObjectSet<Invoice> _invoices;
       
        public ChinookEntities()
            :base("name=ChinookEntities")
        {
        }

        public ObjectSet<Customer> Customers
        {
            get
            {
                if (_customers == null)
                {
                    _customers = base.CreateObjectSet<Customer>();
                }
                return _customers;
            }
        }

        public ObjectSet<Invoice> Invoices
        {
            get
            {
                if (_invoices == null)
                {
                    _invoices = base.CreateObjectSet<Invoice>();
                }
                return _invoices;
            }
        }
    }
}

ObjectContext tipinden türeyen ChinookEntities sınıfı içerisinde Customer ve Invoice sınıflarını kullanan ObjectSet tipli özellikler yer almaktadır. Dikkat edilmesi gereken noktalardan birisi de yapıcı metodun(Constructor) base çağrısı yardımıyla ObjectContext tipinin yapıcısına App.config dosyasında yer alan Connection String bilgisini gönderiyor olmasıdır.

ChinookEntities isimli sınıfı ChinookModel isim alanı(Namespace) içerisinde tasarladığımızdan, Main metodunda gerekli bildirimi yapmamız, biraz önceki kodun değiştirilmeden çalışması için yeterli olacaktır.

using System;
using System.Linq;
using ChinookModel;

namespace POCODans
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                ...

Volaaaa!!! Dikkat edecek olursanız var olan kodu bozmadan ama bu kez POCO nesnelerinden yararlanarak çalıştırmayı başardık. Laughing Bu işlemlerin ardından uygulamamızın tip yapısını Class Diagram üzerinden incelediğimizde, aşağıdaki gibi bir oluşumun söz konusu olduğunu görebiliriz.

Bu yazımızda POCO(Plain Old CLR Objects) nesnelerinin ne olduğunu kısaca tanımaya çalışırken, çok basit bir örnek geliştirerek yolumuza devam ettik. Diğer yandan POCO nesnelerinin, Entity Framework' ün otomatik üretim aracı tarafından oluşturulan Entitiy tipleri ile olan farklarını anlamaya çalıştık. Artık Ado.Net Entity Framework tabanlı projelerimizde POCO nesnelerini kullanma ihtiyacını duyduğumuzda, nasıl hareket etmemiz gerektiğini az çok öğrenmiş olduğumuzu düşünüyorum. Tabi POCO nesnelerini kullanmanın getirdiği bazı dezavantajlar da yok değil. Herşeyden önce EntityObject içerisindeki elemanların türetme olmayışı nedeniyle kullanılamayışı, veritabanı tablolarındaki alan adları ile bir mapping işleminin yapılamayışı başlıca dezavantajlar olarak sayılabilir.

POCO ile ilişkili yeni bilgiler öğrendikçe sizlerle paylaşıyor olacağım. Özellikle bu yazıda henüz değerlendirmediğimiz bir durum var o da Lazy Loading durumlarında POCO nesnelerinin nasıl hazırlanması gerektiği? Bunu bir sonraki yazımızda aynı örnek üzerinden test ederek incelemeye çalışacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

POCO_RTM.rar (44,97 kb) [Örnek Visual Studio 2010 Ultimate RTM sürümü üzerinde geliştirilmiş ve test edilmiştir]

Entity Framework - Entity Bölünmesi (Splitting)

Pazartesi, 5 Nisan 2010 11:15 by bsenyurt

Merhaba Arkadaşlar,

Hız kesmeden Ado.Net Entity Framework ile ilişkili araştırmalarımızı paylaşmaya devam ediyoruz. Bu günkü yazımızda bir Entity tipinin, veritabanı tarafında birden fazla Tablo' yu işaret edebilmesi kabiliyetini incelemeye çalışacağız. Söz konusu duruma çok sık rastlanılmasa dahi, zaman zaman gereksinim duyulmaktadır. Konuyu daha kolay bir şekilde kavrayabilmek adına, yazıyı yazdığım tarih itibariyle sistemimde kurulu olan Adventure Works 2008 veritabanında yer alan ve aşağıdaki diagramda görülen tablolarımızın olduğunu varsayalım(Tabiki kendi sisteminizde yer alan örnek veritabanında benzer ilişikilere sahip başka tabloları da aynı teori içerisinde değerlendirebilirsiniz)

Person, Password ve BusinessEntity tabloları arasındaki ilişkileri değerlendirdiğimizde, 1 Person' a ait 1 Password satırı ve yine 1 Person' a ait 1 BusinessEntity satırı olabileceğini görmekteyiz. Aslında bu tabloların tamamı tek bir Person verisini ifade etmekte. Tabi veritabanı tarafında kavramsal olarak bu veri birden fazla tabloya ayrıştırılmış durumda. Çok doğal olarak kod tarafına geçtiğimizde bu tabloların her biri için ayrı birer Entity tipi üretileceğine şahit olacağız. Aynen aşağıdaki Entity Model Diagram' da görüldüğü gibi.

Tabi kod tarafında Person içeriğini değerlendirirken Password veya BusinessEntity içeriklerine de kolay bir şekilde erişebilmek gibi bir amacımız varsa eğer, 3 farklı Entity yerine 1 Entity kullanmak daha avantajlı olabilir. Özellikle kodlama zamanında yazılan LINQ sorgularında veya CRUD işlemlerinde. Peki bu 3 Entity' nin tek bir Entity olarak ele alınması için ne yapmak gerekmektedir? Son derece basit. Cut-Paste özelliklerinden faydalanılmalıdırWink Bu anlamda, BusinessEntity ve Password Entity içeriklerine baktığımızda bizim için gerekli olan alanları kesip Person Entity tipi içerisine yapıştırmamız yeterlidir. Ne yazık ki bu örnekte yer alan BusinessEntity tablosunun içeriğinde çok faydalı bilgiler bulunmamaktadır. Ama yinede konunun kavranabilmesi açısından değerlendirilmiştir. Bu işlemleri gerçekleştirirken dikkat edilmesi gereken noktalaradan biriside, aynı isimli Property' lerin kopyalanması sonrasında, son ek olarak _1 gibi sayısal bir isimlendirmenin söz konusu olmasıdır. Tabiki bu tip özellikleri tekrardan isimlendirmekte yarar vardır. Gerekli kesme ve yapıştırma işlemleri tamamlandıktan sonra Password ve BusinessEntity tipleri diagramdan silinebilir. Yine bu işlem sırasında dikkat edilmesi gereken bir noktada bize sorulan soruya No cevabını vermektir.(Sorunun ne olduğunu söylemeyeceğim, örneği yaparken görmenizi istiyorum. Wink ) Sonuç olarak diagramın yeni hali aşağıdaki gibi olacaktır.

Yeterli mi? Elbette değil. Designer' da yapmış olduğumuz taşıma işlemlerine rağmen, Mapping Details içeriğinde halen daha eksiklikler bulunmaktadır. Aşağıdaki şekilde bu durum net bir şekilde görülebilir.

Eksik olan kısım şudur; kopyalanan özelliklerin hangi tablolardaki hangi alanları işaret ettiği belli değildir. Dolayısıyla Mapping Details kısmında gerekli düzenlemeleri yaparak aşağıdaki şekilde görülen eşleştirmeleri tamamlamamız gerekir.

Buna göre Person Entity tipinin Person tablosu dışında BusinessEntity ve Password tablolarını da işaret ettiği belirtilmiş olur. Gerekli Field-Property eşleştirmeleri yapıldıktan sonra(örneğin rowguid' in BusinessEntity_RowGuid ile eşleşmesi gibi) uygulama derlenip örnek bir kodun denenmesi için gerekli işlemlere başlanabilir. Bu amaçla Main metodu içerisinde aşağıdaki test kodunu yazdığımızı düşünelim.

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

namespace Splitting
{
    class Program
    {
        static void Main(string[] args)
        {
            using (AdventureWorks2008Entities entities = new AdventureWorks2008Entities())
            {
                var result = (from p in entities.People
                             select new
                             {
                                 p.PersonType,
                                 p.FirstName,
                                 p.MiddleName,
                                 p.LastName,
                                 p.PasswordHash,
                                 p.PasswordSalt,
                                 p.BusinessEntity_rowguid,
                                 p.Password_ModifiedDate
                             }).Take(3);

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString()+"\n");
                }
            }
        }
    }
}

Örneğimizde ilk 3 satıra ait alanlardan bazılarının çekildiği isimsiz tip(Anonymous Type) kullanımı söz konusudur. Dikkat edilmesi gereken nokta ise tek bir Entity nesnesi üzerinden, Person, Password ve BusinessEntity tablolarının ilgili alanlarına ulaşılabiliyor olmasıdır ki bu Splitting özelliğinin bir sonucudur. Buna göre örneğimizin çalışma zamanı çıktısı aşağıadaki gibi olacaktır.

Bu kabiliyetin uygulanışı sırasında en çok dikkat edilmesi gereken nokta arka planda çalıştırılan SQL sorgularıdır. Nitekim birden fazla Tablonun tek bir Entity içerisinde birleştirilmesi arka planda Inner Join sorgularının atılmasına neden olacaktır. Yukarıdaki örneğin çalışma zamanı sırasında SQL tarafında icra edilen sorgudan bu durum net bir şekilde görülebilir.

SELECT TOP (3)
[Extent1].[BusinessEntityID] AS [BusinessEntityID],
[Extent3].[PersonType] AS [PersonType],
[Extent3].[FirstName] AS [FirstName],
[Extent3].[MiddleName] AS [MiddleName],
[Extent3].[LastName] AS [LastName],
[Extent2].[PasswordHash] AS [PasswordHash],
[Extent2].[PasswordSalt] AS [PasswordSalt],
[Extent1].[rowguid] AS [rowguid],
[Extent2].[ModifiedDate] AS [ModifiedDate]
FROM   [Person].[BusinessEntity] AS [Extent1]
INNER JOIN [Person].[Password] AS [Extent2] ON [Extent1].[BusinessEntityID] = [Extent2].[BusinessEntityID]
INNER JOIN [Person].[Person] AS [Extent3] ON [Extent1].[BusinessEntityID] = [Extent3].[BusinessEntityID]

Dolayısıyla bu çalışma şekli dikkate alınarak gerçekten gereksinim var ise Entity birleştirmesi yolu tercih edilmelidir. Öyleki Entity tiplerinin ayrık olarak kalmaları tercih edilebilir. Söz gelimi geliştirdiğimiz örnekte BusinessEntity tablosundan alınan alanların program kodu içerisinde hiç bir yerde kullanılmadığı bir durumda, bu tablo alanlarının Person Entity tipi içerisine alınması anlamsızdır. Nitekim, performans açısından gereksiz Inner Join sorgusu atılmasına neden olmaktadır. 

Sonuç olarak Splitting özelliği yardımıyla bir Entity içerisinde yer alan özelliklerin(Property), veritabanı tarafında birden fazla Tablo' nun alanlarına(Fields) ait olması sağlanabilmektedir. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Splitting_RC.rar (49,90 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Entity Framework - Many To Many Relations - Link Tablosunda Israrcı Olmak

Çarşamba, 24 Mart 2010 09:35 by bsenyurt

Merhaba Arkadaşlar,

Bazen bebek adımları ile ilerlememiz gerekir. Özellikle yazılım alanında bazı konuları öğrenirken işin teorisinden önce pratik bir örneği adım adım geliştirmek son derece faydalıdır. Ado.Net Entity Framework ile ilişkili inclemelerimize devam edeceğimiz bu yazımızda bebek adımları ile ilerleyeceğiz.

Hatırlayacağınız üzere son iki yazımızda Many-To-Many ilişkileri nasıl ele alabileceğimizi incelemiştik. Many-To-Many ilişkilerin Entity Model'e olan yansımasında belkide en önemli nokta, ara bağlantı tablosunun taşınmıyor olmasıydı. Bu genellikle ara bağlantı tablosu üzerinde diğer tablolara ait Primary Key alanlarının bulunduğu durumlar düşünülerek meydana gelen bir sonuçtur. Nitekim ara tablonun Entity tarafına taşınmayışının herhangibir olumsuz maliyeti bulunmamaktadır. Ancak bazı durumlarda söz konusu ara bağlantı tablosunun ilerleyen zamanlarda ek alanlar ile genişlemesi söz konusu olabilir. Bu durumda ara bağlantı tablosunun Entity olaraktan Model Diagram içerisinde yer almasının avantajı olacaktır. Nitekim ileride eklenecen kolonlar için Entity Model tarafında sadece Conceptual Schema' yı güncellemek yeterlidir.

Dolayısıyla bu yazımızdaki amacımız, varsayılan olarak Entity Model Diagram üretilirken ortadan kaldırılan ara bağlantı tablosunun manuel olarak oluşturulmasını sağlama olacaktır. Başlangıçta Visual Studio 2010 Ultimate RC sürümü üzerinde oluşturduğumuz Console uygulaması içerisinde yer alan Entity Model diagramının aşağıdaki gibi olduğunu varsayıyoruz.

Tahmin edeceğiniz üzere Chinook veritabanı içerisinde yer alan Playlist-Track ilişkilerini değerlendirmekteyiz. Şekil üzerinde yer alan çarpı işareti mutlaka dikkatinizi çekmiştir. Aslında bu yapacağımız ilk hamle olacak. Aradaki Association nesnesini silmek. Bu durumda çok doğal olarak Playlist ve Track tablolarında yer alan ve birbirlerine olan geçişleri sağlayan Navigation Property' lerin de ortadan kaldırıldığını göreceğiz.

Ancak burada dikkat etmemiz gereken bir nokta bulunmaktadır. Silme işlemi sırasında Designer' ın aşağıdaki sorusu ile karşılaşacağız. PlaylistTrack tablosu her ne kadar Entity olarak ifade edilmese de, oluşturacağımız Entity' nin ileride veritabanında karşılık geldiği tablo ile ilişkilendirilmesi sırasında ele alınacaktır. Bu nedenle No seçimi yaparaktan silme işlemini icra etmemiz daha doğrudur.

Sıradaki adımımız ise veritabanında yer alan ve Playlist ile Track tabloları arasındaki Many-To-Many ilişkiyi gerçekleştiren tabloya karşılık gelen bir Entity tipinin oluşturulmasıdır. Yani veritabanında yer alan PlaylistTrack isimli tablonun karşılığı olan Entity tipinin üretilmesi. Bunun için Model diagramı üzerinden Add->Entity seçimini yapmamız yeterli olacaktır. Sonrasında ise Entity özelliklerini aşağıdaki gibi ayarlamamız gerekecektir.

 

Burada dikkat edilmesi gereken noktalardan birisi, Key Property özelliğinin kullanılmayışıdır. Nitekim bu alana senaryomuza göre gerek yoktur. Ara tablonun karşılığı olan PlaylistTrack Entity tipinin görevi, Playlist ve Track Entity tipleri arasındaki Many-To-Many ilişkiyi sağlamak olduğundan, bu Entity' lere ait Key özelliklerini barındırması yeterlidir. Buna göre yeni oluşturulan Entity içerisinde aşağıdaki şekilde görülen Scalar Property' lerin eklenmesi gerekir.

Yine dikkat edilmesi gereken önemli bir nokta vardır. PlaylistTrack Entity tipi içerisinde yer alan PlaylistId ve TrackId isimli alanlarının Entity Key özelliklerine true değer atanmıştır. Buna göre bu alanların Playlist ve Track tablosundaki karşılıkları ile ilişki kurabilmesi mümkün olacaktır. Dolayısıyla sıradaki adımımız tablolar arasındaki ilişkileri kurmaktır.

Not : Aslında PlaylistId ve TrackId alanlarının oluşturulması şart değildir. PlaylistTrack tablosuna ait bilgileri silmediğimizden Association' ları oluşturduğumuz sırada söz konusu alanların otomatik olarak üretileceğini görebiliriz. Bu tekniğide tercih edebilirsiniz. Şu andaki ilerleyişimize göre Association' ların oluşturulması sırasında Add foreign key properties to the... seçeneğini kaldırmamız gerekmektedir. Ancak kolonların oluşturulmasını otomatikleştirmek istiyorsak, bu seçeneği işaretli bırakmamız gerekmektedir. Böyle yaptığımız takdirde kolon adlarının pekte istediğimiz gibi olmayacağını, yeniden isimlendirmemiz gerektiğinide belirtmek isterim. Tercih size kalmış. Wink

Şimdi bir düşünelim. Bir Playlist'e bağlı birden fazla Track olabilir. Benzer şekilde bir Track birden fazla Playlist içerisinde yer alabilir. Buna göre Playlist ve Track varlıkları arasında Many-To-Many ilişki söz konusudur. Bunu zaten biliyoruz Smile Ancak bunun Entity Model diagram tarafında, şu andaki yapıya göre ifadesi nasıl olacaktır? Cevap; Playlist Entity' sinden PlaylistTrack Entity' sine ve benzer şekilde Track Entity' sinden yine PlaylistTrack Entity' sine doğru One-To-Many ilişki sağlayarak. Bunun için diagrama bir Association nesnesi eklenmesi yeterlidir(Add->Association).

Playlist -> PlaylistTrack

Dikkat edileceği üzere End özelliklerinden sol tarafta yer alanda Playlist bulunmaktadır. Playlist Entity tipi için Multiplicity özelliği 1(One) iken, sağ tarafta yer alan PlaylistTrack için *(Many) dir. Buna göre Playlist Entity tipinden PlaylistTrack Entity tipine doğru One-To-Many ilişki kurulmuştur. Üretilen Navigation Property' lerden PlaylistTrack olarak adlandırılanlar, çoğulluk nedeni ile PlaylistTracks olarak yeniden isimlendirilmiştir. Bu işlemin sonrasında diagramın yeni hali aşağıdaki gibi olacaktır.

Track -> PlaylistTrack

Bu kez Track Entity tipi üzerinden az önceki gibi PlaylistTrack Entity tipine doğru One-To-Many ilişki kurulmuştur. Sonuç olarak diagramın yeni hali aşağıdaki gibi olacaktır.

Sıradaki adımda oluşturulan yeni PlaylistTrack Entitiy tipinin, veritabanında yer alan Tablo karşılığı ile eşleştirilmesi gerekir. Bu sebepten PlaylistTrack Entity tipinin Mapping Details özelliklerinden aşağıdaki gibi gerekli eşleştirmenin yapılması şarttır. Hatırlayacağınız üzere ilk adımda Association nesnesini silerken tablonun kaldırılmamasını belirtmiştik. Bu sayede Table eşleştirmesini kolayca gerçekleştirdik.

Ancak işlemlerimiz bir türlü bitmek bilmemektedir. Sabredin, çok az kaldı. Takip eden adımda Referentail Constraint' lerin tanımlanması gerekmektedir. Normal şartlarda eğer Association' lar oluşturulurken Add foreign key properties to the...  işaretli olsaydı bunlarda ilgili alanlar ile birlikte otomatik oluşturulacaklardı. Bu nedenle izleyen şekillerde görüldüğü üzere ilgili eklemelerin yapılması gerekmektedir.

ve

Buraya kadar yaptıklarımızı özetleyecek olursak aşağıdaki adımları gerçekleştirdiğimizi ifade edebiliriz.

1- Entity' ler arasındaki Many-To-Many tipinden Relation silinir.

2- Veritabanında yer alan ama Entity Model tarafına aktarılmayan ara tablonun karşılığı olan tipin üretilmesi sağlanır.

3- Eklenen Entity tablosuna Entity Key özellikleri true olan ve diğer Entity' ler üzerinde karşılık düşen alanlar ile aynı tipten olan(Örneğimizde hepsi Int32) Scalar Property' ler ilave edilir.

4- Entity tipleri arasındaki One-To-Many ilişkiler(Association) tesis edilir.

5- Eklenen Entity nesnesi, veritabanındaki tablo ile ilişkilendirilir(Mapping).

6- Association' lar ile ilişkili olaraktan gerekli Referencial Constraints' ler tanınmlanır.

Artık konuyu basit bir örnek sonlandırabiliriz. Bu amaçla aşağıdaki program kodunu yazdığımızı düşünelim.

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

namespace RelationEntityCreation
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                var result = (from p in entities.Playlists.Include("PlaylistTracks.Track")
                              where p.PlaylistId == 3
                              select p).First();


                foreach (var r in result.PlaylistTracks)
                {
                    Console.WriteLine(r.Track.Name);
                }
            }
        }
    }
}

Örnek kod parçasında PlaylistId değeri 3 olan satıra bağlı olan Track' lerin çekilmesi sağlanmaktadır. Include metodu içerisinde belirtilen tanımlama önemlidir. Burada PlaylistTracks üzerinden Track Entity' sine ulaşılmaktadır. Bu sayede Playlist' e bağlı Track' lerinde yüklenmesi sağlanmış olur. Uygulama kodunun çalışmasının sonucu aşağıdaki gibidir.

Tabi bu kodun çalışması sonrasında SQL tarafında oldukça yoğun bir sorgunun üretilmesi söz konusudur.

SELECT
[Project1].[PlaylistId] AS [PlaylistId],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[PlaylistId1] AS [PlaylistId1],
[Project1].[TrackId] AS [TrackId],
[Project1].[TrackId1] AS [TrackId1],
[Project1].[Name1] AS [Name1],
[Project1].[AlbumId] AS [AlbumId],
[Project1].[MediaTypeId] AS [MediaTypeId],
[Project1].[GenreId] AS [GenreId],
[Project1].[Composer] AS [Composer],
[Project1].[Milliseconds] AS [Milliseconds],
[Project1].[Bytes] AS [Bytes],
[Project1].[UnitPrice] AS [UnitPrice]
FROM ( SELECT
 [Limit1].[PlaylistId] AS [PlaylistId],
 [Limit1].[Name] AS [Name],
 [Join1].[PlaylistId] AS [PlaylistId1],
 [Join1].[TrackId1] AS [TrackId],
 [Join1].[TrackId2] AS [TrackId1],
 [Join1].[Name] AS [Name1],
 [Join1].[AlbumId] AS [AlbumId],
 [Join1].[MediaTypeId] AS [MediaTypeId],
 [Join1].[GenreId] AS [GenreId],
 [Join1].[Composer] AS [Composer],
 [Join1].[Milliseconds] AS [Milliseconds],
 [Join1].[Bytes] AS [Bytes],
 [Join1].[UnitPrice] AS [UnitPrice],
 CASE WHEN ([Join1].[PlaylistId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
 FROM   (SELECT TOP (1) [Extent1].[PlaylistId] AS [PlaylistId], [Extent1].[Name] AS [Name]
  FROM [dbo].[Playlist] AS [Extent1]
  WHERE 3 = [Extent1].[PlaylistId] ) AS [Limit1]
 LEFT OUTER JOIN  (SELECT [Extent2].[PlaylistId] AS [PlaylistId], [Extent2].[TrackId] AS [TrackId1], [Extent3].[TrackId] AS [TrackId2], [Extent3].[Name] AS [Name], [Extent3].[AlbumId] AS [AlbumId], [Extent3].[MediaTypeId] AS [MediaTypeId], [Extent3].[GenreId] AS [GenreId], [Extent3].[Composer] AS [Composer], [Extent3].[Milliseconds] AS [Milliseconds], [Extent3].[Bytes] AS [Bytes], [Extent3].[UnitPrice] AS [UnitPrice]
  FROM  [dbo].[PlaylistTrack] AS [Extent2]
  INNER JOIN [dbo].[Track] AS [Extent3] ON [Extent2].[TrackId] = [Extent3].[TrackId] ) AS [Join1] ON [Limit1].[PlaylistId] = [Join1].[PlaylistId]
)  AS [Project1]
ORDER BY [Project1].[PlaylistId] ASC, [Project1].[C1] ASC

Buuuwvvvvv!!!  Sealed Açıkçası ben bu tip bir tablonun eğer maliyet kaybı yoksa ısrarla eklenmesinden yana değilim. Yani Ado.Net Entity Framework gibi düşünüyorum. Fakat yazımızın başında da belirttiğimiz üzere ara tablonun, One-To-Many ilişkiler için gerekli olanlar dışında ek alanlar da içerebileceği durumlar söz konusu olabilir. Bu alanların zaman içerisinde tabloya eklenmesi ve Entity tarafına da taşınması gerektiği hallerde, Entity Model üzerinde söz konusu ara tablonun karşılığının olması, sadece Conceptual Model üzerinde müdahaleler yaparak işi kurtarmamızı sağlayabilir. Teorik olarak. Yine de anlattıklarımızın RC sürümü üzerinde yazıldığını belirtmek isterim. Yani ilerleyen sürümde farklılıklar söz konusu olabilir. Wink Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

RelationEntityCreation_RC.rar (52,15 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Screencast - Entity Framework, WCF RIA Services, Silverlight 4.0

Cuma, 19 Mart 2010 22:56 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere 2010 yılı Microsoft geliştiricileri açısından epey bir hareketli başladı. Aslında ayak sesleri 2008 yılındaki Profesyonel Geliştiriciler Konferansında(Microsoft PDC) duyulan pek çok ürünün artık nihai sürümlerinin çıkacağı günlere yaklaşıyoruz. Aldığımız duyumlar Nisan ayı içerisinde .Net Framework 4.0 ve Visual Studio 2010 tarafında Relase sürümlerinin en azından RTM sürümlerinin çıkacağı yönünde. 2008 ve belki de daha öncesinden beri süre gelen zaman içerisinde beni en çok şaşırtan ürünlerinden birisi de Silverlight. Daha dün gibi ilk versiyonunu hatırladığımız ürünün geçtiğimiz günlerde MIX2010 ile birlikte 4.0 sürümünün çıktığı duyruldu. Visual Studio 2010 Beta 2 ile kullanıp bakabildiğimiz ama RC sürümünde kullanılamayan sürüm artık kullanılabilir halde Wink Bunun için Silverlight resmi sitesinden gerekli kurulumları yapmanız yeterli olacaktır. Şu aşamada Visual Studio 2010 RC ve Visual Studio 2008 SP1 sürümlerinde ele alabiliyorıuz.

Silverlight tarafında beni en çok ilgilendiren konuların başında ise sunucu kaynaklarının istemci tarafından kullanılabilmesinde önemli bir rol üstelenen WCF RIA Service' ler gelmekte. Eski adıyla .NET RIA Service' lerin WCF Eco System içerisinde WCF RIA Service olarak anılmaya başlandığını biliyoruz. Daha öncesinde WCF RIA Service' ler ile ilişkili çeşitli blog yazılarım oldu ancak görsel anlatım ne denli güçlü olduğunu hepimiz gayet iyi biliyoruz. İşte bu felsefe ile ve NedirTv? desteğiyle hazırladığımız bu görsel dersimizde WCF RIA Service' lerinin kullanımına dair basit bir Hello World uygulaması geliştiriyor olacağız. Üstelik Domain Service tarafında Entity Framework 4.0 sağlayıcısından yararlanarak veri sunumunu gerçekleştireceğiz. İyi seyirler dilerim.

Boyut : 23.2 Mb

Süre : 15:32

Download Etmek veya İzlemek İçin

SilverlightApplication2.rar (2,37 mb) [Örnek Visual Studio 2010 RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Entity Framework - Many To Many Relations - Link Tablosunu Okumak

Salı, 16 Şubat 2010 09:30 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız üzere bir önceki yazımızda veritabanı tarafındaki Many-To-Many Relation' ların, Entity Framework tarafında nasıl değerlendirilebileceğine değinmeye çalışmıştık. Dikkat edilmesi gereken önemli noktalardan birisi, veritabanı tarafında yer alan ara bağlantı tablosunun Entity Framework üzerindeki modele alınmayışıydı. Ancak tam bu noktada ortaya bir soru işareti çıkmakta. Ya çalışma zamanında sadece ilişkili primary key alanlarının değerlerini elde etmek istersek. Bir önceki senaryomuza göre bu, hangi Track satırının hangi Playlist' ler ile ve hangi Playlist satırının hangi Track' ler ile ilişkili olduğunun görülmesi anlamına gelmektedir. Kısacası sadece TrackId ve PlaylistId değerlerinin eşleşmesine bakmak istediğimizi düşünebiliriz. Bu durumda aşağıdaki gibi bir kod parçası işimizi görecektir. Şükürler olsun isimsiz tiplere(Anonymous Type)Wink 

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Only Related Columns

                var relations = from t in entities.Tracks
                                from p in entities.Playlists
                                where t.Name.StartsWith("Ab")
                                select new
                                {
                                    t.TrackId,p.PlaylistId
                                };

                foreach (var relation in relations)
                {
                    Console.WriteLine(relation.ToString());
                }

                #endregion
            }
        }
    }
}

Bu LINQ sorgusunda çoklu Select işlemi yapılmaktadır. Sorguya göre Track.Name değeri Ab ile başlayan parçaların TrackId değeleri ile Playlist Entity nesnesi içeriğinde yer alan PlaylistId değerleri arasındaki ilişkiler, yeni bir isimsiz tip(Anonymous Type) içerisine dahil edilmektedir. Kodun çalışması sonucu aşağıdakine benzer bir çalışma zamanı çıktısı elde edilecektir.

Arka planda çalışan SQL Sorgusu ise aşağıdaki gibidir.

SELECT
[Extent1].[TrackId] AS [TrackId],
[Extent2].[PlaylistId] AS [PlaylistId]
FROM  [dbo].[Track] AS [Extent1]
CROSS JOIN [dbo].[Playlist] AS [Extent2]
WHERE [Extent1].[Name] LIKE N'Ab%'

Görüldüğü üzere Anonymous Type kullanımı nedeniyle sadece istediğimiz TrackId ve PlaylistId alanları Select sorgusuna dahil edilmiştir. Buda arka planda çalışan SQL sorgusunda, tüm tablo alanlarının hesaba katılmaması anlamına gelmektedir ki bizim için bir avantajdır ve istediğimizde zaten budur Wink Bir nevi veritabanında yer alan ama Entity olarak kod tarafına aktarılmayan ara tablo içeriğini, LINQ tarafında elde etmiş olduk. Entity Framework tarafında ipucu tadındaki başka konularlar devam ediyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Entity Framework - Many To Many Relations

Cuma, 12 Şubat 2010 14:05 by bsenyurt

Merhaba Arkadaşlar,

Bundan yıllar önce üniversiteden arkadaşım Orkun Şentürk ile birlikte Altunizade Capitol alışveriş merkezinde Dreamcatcher isimli bir bilim kurgu gerilim filmine gittiğimizi hatırlıyorum. (Aslında bilim kurgu filmlerin tam bir hayranıyımdır. Ancak seyrettiklerimin hiç biri Starwars veya Terminator gibilerinin yerini tutmamakta) Film Stephen King' in bir romanından uyarlanmıştı. Sevgili arkadaşım Orkun kitabını okuyarak geldiğinden, film üzerinde daha iyi yorumlar yapmıştı ancak ortak kanımız çok başarılı olmadığı yönündeydi. Açıkçası fazla etkilenmemiştik. Özellikle filmden çıktıktan sonra yaptığımız muhabbet bunu doğrular nitelikteydi. .Net Framework 1.1 ile yazmakta olduğumuz programda kullandığımız SQL 2000 veritabanındaki many-to-many ilişkileri anlamaya çalışıyorduk. O zamanlar bizim için SQL veritabanına bağlanabilmek bile bir lüks iken bu tip alengirli konular korkutucu geliyordu. Filmden daha çok gerildiğimiz bir konuydu.Tongue out Sonuçta bende sevgili Orkun' da evlerimizin yolunu tuttuğumuzda, odamızda bizi bekleyen masa üstü bilgisayarlar ile ne yapacağımızı gayet iyi biliyorduk. Many-To-Many olayını kavramak.

Gerçektende yazılımın ilk yıllarında hepimiz benzer durumları tecrübe etmekteyiz. Pek çok konuyu anlamakta, öğrenmekte güçlük çekiyoruz. Tabi içimizdeki öğrenme arzusunun gücüne bağlı olaraktan, ya çok çalışarak ya da önemli ve dikkat edilmesi gereken noktaları anında yakalayarak ilerleyişimizi sürdürüyoruz. Bir yazılımcı, yıllar sonra geldiği noktadan geriye doğru dönüp baktığındaysa, ilerleyişinin ne kadar hızlı olduğunu net bir şekilde görebilir aslında. Ancak bu yeterli değildir. Dilerseniz sözü fazla uzatmadan bu günkü konumuza geçelim. Bu gün yine Many-To-Many ilişkileri inceliyor olacağız. Ancak bu kez olayı Entity Framework üzerinden değerlendireceğiz. Başlamadan önce veritabanı üzerindeki tablolar arası ilişkilerden(Relations) kısaca bahsetmekte yarar olduğu kanısındayım.

Genel olarak tablolar arasında bire bir(one-to-one), bire çok(one-to-many), çoğa çok (many-to-many) ve self referencing ilişkilerden bahsedilmektedir. Bire-bir ilişkilerde tabloda yer alan bir satırın diğer tabloda yine tek bir satır ile ilişkilendirilmesi söz konusudur. Yaygın olarak kullanılan bire-çok ilişkilerde ise, bir tablodaki satıra diğer bir tablodan n adet satırın bağlanabilmesi mümkündür. Söz gelimi ürün kategorilerinin tutulduğu tablodaki bir satırın, ürünlerin tutulduğu tablodaki n satırı referans etmesi gibi. Self-Referencing ilişkilerde ise bir tablonun herhangibir satırının/satırlarının, kendi içerisindeki bir satırı referans etmesi durumu söz konusudur. Örneğin bir şirketin organizasyon ağacında yer alan bir personelin/personel topluluğunun kime bağlı olduğunun tutulduğu tablolarda, bu tip ilişkiler kullanılabilir.

Gelelim çoğa-çok ilişkilere. Örneğin filmler ve oyunculara ait tablolar olduğunu düşünelim. Burada bir oyuncunun birden fazla filmde rol alması veya bir film içerisinde birden fazla oyuncunun bulunması çok normal ve olasıdır. Bu sebepten her iki tablo birbirleri üzerinde n sayıda satırı referans edebilmektedir. Bu tip bir durumda tablolar arasındaki ilişkiyi ifade etmek için ek bir tablonun kullanılması söz konusudur. Bu tablo üzerinden sağlanan ilişkiler sayesinde çoğa-çok ilişkinin gerçeklenmesi mümkün olabilir. Peki SQL tarafında ek bir tablo yardımıyla ele alınan bu ilişkilerin Entity Framework tarafındaki yansıması nasıldır? Gelin bu durumu basit bir örnek yardımıyla incelemeye çalışalım.

İlk olarak Chinook veritabanında yer alan ve SQL tarafındaki diagramda aşağıdaki şekilde görülen ilişkilere sahip olan tabloları kullanacağımızı belirtelim.

Buradaki senaryoda Playlist ve Track tabloları arasında çoğa-çok ilişki olduğu görülmektedir. Bir başka deyişle bir Playlist birden fazla Track satırına referans edebileceği gibi, bir Track satırı da birden fazla Playlist satırına referans edebilir. Şimdi bu ilişkilerin Entity Framework tarafındaki oluşumuna bir bakalım. (Bu amaçla Visual Studio 2010 Ultimate RC ve Ado.Net Entity Framework 4.0 sürümlerini kullanıyor olacağız. Ancak bu konunun Entity Framework' ün önceki sürümünde de aynen geçerli olduğunu hatırlatalım.) Özellikle 3 tabloyu da seçtiğimizi düşünelim.

Dikkat edileceği üzere Playlist, Track ve PlaylistTrack tablolarının tamamı seçilmiştir. Sihirbaz adımlarını tamamladığımızda ise, aşağıdaki Model diagramının oluştuğunu görürüz.

Hımmm...Wink Dikkat edileceği üzere PlaylistTrack tablosu model diagramına dahil edilmemiştir. Neden?

SQL tarafına bakıldığında PlaylistTrack tablosu üzerinde sadece Playlist ve Track tablolarına ait Primary Key alanları bulunmaktadır. Bunlar dışında ek bir alan yoktur. Bir başka deyişle veritabanı tarafında Playlist ve Track tablolarının many-to-many ilişkilerinin sağlandığı tablodur. Ancak Entity Framework tarafında tabloların sınıflar yardımıyla ve bu sınıfların referans ettikleri diğer sınıfların ise özellikler yardımıyla belirtildiği bilinmektedir. Dolayısıyla Entity Framework tarafında PlaylistTrack isimli bir sınıfın oluşturulmasının bir anlamı yoktur. Dahası olmamasının bir kaybı da yoktur. Bu yüzden Playlist ve Track sınıfları birbirlerine EntityCollection<T> tipinden olan navigasyon özellikleri yardımıyla(Tracks ve Playlists) doğrudan bağlanmışlardır. Bu durum ilişkiyi sağlayan bileşenin(Association nesnesi) özelliklerine bakıldığında da net bir şekilde görülebilir.

Şimdi kod tarafında Many-To-Many ilişkileri nasıl ele alacağımıza bakmaya çalışalım. Örneğin TV-Show listesine ait Track bilgilerini getirmek istediğimizi düşünelim. Bunun için aşağıdaki gibi bir kodlama yapabiliriz.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                Playlist result = (from pList in entities.Playlists
                                   where pList.PlaylistId == 10
                                   select pList).First();


                Console.WriteLine("{0}-{1}", result.PlaylistId, result.Name);
                foreach (var t in result.Tracks)
                {
                    Console.WriteLine("\t {0}[{1}]", t.Name, t.Milliseconds.ToString());
                }
            }
        }
    }
}

Buna göre aşağıdaki sonuçları elde ederiz.

Bu kod parçasının çalışması sonucunda SQL tarafında, aşağıdaki sorgunun oluşturulduğu gözlemlenecektir.

İlk önce Playlist bilgisinin çekilmesi,

SELECT TOP (1)
[Extent1].[PlaylistId] AS [PlaylistId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Playlist] AS [Extent1]
WHERE 10 = [Extent1].[PlaylistId]

sonrasında ise ilgili Playlist' e bağlı Track' lerin Inner Join sorgusu ile elde edilmesi gerçekleşecektir.

exec sp_executesql N'SELECT
[Extent2].[TrackId] AS [TrackId],
[Extent2].[Name] AS [Name],
[Extent2].[AlbumId] AS [AlbumId],
[Extent2].[MediaTypeId] AS [MediaTypeId],
[Extent2].[GenreId] AS [GenreId],
[Extent2].[Composer] AS [Composer],
[Extent2].[Milliseconds] AS [Milliseconds],
[Extent2].[Bytes] AS [Bytes],
[Extent2].[UnitPrice] AS [UnitPrice]
FROM  [dbo].[PlaylistTrack] AS [Extent1]
INNER JOIN [dbo].[Track] AS [Extent2] ON [Extent1].[TrackId] = [Extent2].[TrackId]
WHERE [Extent1].[PlaylistId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=10

Birde ters tarafan gitmeye çalışalım. Örneğin bir Track' ın geçtiği Playlist' leri bulmak istediğimizi düşünelim. Bu durumda aşağıdaki gibi bir kod parçasını göz önüne alabiliriz.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                Track track = (from t in entities.Tracks
                               where t.TrackId == 1
                               select t).First();

                Console.WriteLine("{0}-{1}-[{2}]",track.TrackId.ToString(),track.Name,track.Milliseconds.ToString());

                foreach (var p in track.Playlists)
                {
                    Console.WriteLine("\t{0}-{1}",p.PlaylistId.ToString(),p.Name);
                }
            }
        }
    }
}

Bu kod parasına göre, çalışma zamanında TrackId değeri 1 olan parçanın geçtiği Playlist' lerin listesini elde edebildiğimizi görürüz.

Bu kod parçası ise bir öncekine benzer olaraktan aşağıdaki SQL sorgularının çalıştırılmalarına neden olacaktır.

Önce Track bilgileri çekilecek,

SELECT TOP (1)
[Extent1].[TrackId] AS [TrackId],
[Extent1].[Name] AS [Name],
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[MediaTypeId] AS [MediaTypeId],
[Extent1].[GenreId] AS [GenreId],
[Extent1].[Composer] AS [Composer],
[Extent1].[Milliseconds] AS [Milliseconds],
[Extent1].[Bytes] AS [Bytes],
[Extent1].[UnitPrice] AS [UnitPrice]
FROM [dbo].[Track] AS [Extent1]
WHERE 1 = [Extent1].[TrackId]

sonrasında ise ilgili Track satırının geçtiği Playlist satırlarının bulunması için gerekli Inner Join sorgusu çalıştırılacaktır.

exec sp_executesql N'SELECT
[Extent2].[PlaylistId] AS [PlaylistId],
[Extent2].[Name] AS [Name]
FROM  [dbo].[PlaylistTrack] AS [Extent1]
INNER JOIN [dbo].[Playlist] AS [Extent2] ON [Extent1].[PlaylistId] = [Extent2].[PlaylistId]
WHERE [Extent1].[TrackId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

Pek tabi olarak Many-to-Many ilişki söz konusu olan tablolar üzerinden Update, Delete veya Insert işlemleri de yapılabilir. Burada, özellikle Delete operasyonlarında tarafların nasıl tepki göstereceği önemlidir. Normal şartlarda Association nesnesi üzerinde yer alan End1 OnDelete ve End2 OnDelete isimli özelliklerin değerleri none olarak belirlenmiştir. Ancak istenirse bunlar Cascade değerine çekilebilir. Pek tabi ilişkinin nasıl bir tepki vereceğine bağlı olaraktan SQL tarafından Foreign Key' ler ile alakalı istisnaların alınması da muhtemeldir. Şimdi bu durumu incelemeye çalışalım.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Delete and Foreign Key Violation

                var trackOnDelete = (from t in entities.Tracks
                                     where t.TrackId == 3502
                                     select t).First();

                entities.DeleteObject(trackOnDelete);

                entities.SaveChanges();

                #endregion
            }
        }
    }
}

Bu kod parçasına göre TrackId değeri 3502 olan Track satırı silinmeye çalışılmaktadır. Ancak Track' lar çok doğal olarak Playlist' lere bağlıdır. Bu nedenle silme işlemi sırasında aşağıdaki çalışma zamanı istisnası(Runtime Exception) alınacaktır.

Burada sebep, söz konusu alan için bir Relation' ın var olmasıdır. Dolayısıyla öncelikle silinmek istenen Track satırı ile bağlı olduğu Playlist satırları arasındaki ilişkileri kaldırmak gerekmektedir. Silme operasyonunun gerçeklenmesi için, silinmek istenen Track' ın dahil olduğu Playlist nesnelerini bulup, herbirinin Tracks özelliği üzerinden Remove metodunun çalıştırılması yeterlidir. Mi acaba? Undecided 

Bu tip bir kod yazılmak istendiğinde elde edilen Playlist.Tracks özellikleri üzerinden yapılan silme hareketleri, koleksiyonunun değişmesine neden olacağından, çalışma zamanında Concurrency hatası alınacaktır. Çok şükür ki artık elimizin altında .Net Framework 4.0 içerisine gömülü olarak gelen Concurrent koleksiyonlar bulunmaktadır.(Concurrent Collections (Eş Zamanlı Koleksiyonlar) [Beta 1] , Concurrent Collections : Macera BlockingCollection ile Devam Ediyor [Beta 1] ) Buna göre kodu aşağıdaki gibi geliştirebiliriz.

using System;
using System.Linq;
using System.Collections.Concurrent;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Delete and Foreign Key Violation

                var trackOnDelete = (from t in entities.Tracks
                                     where t.TrackId == 3503
                                     select t).First();

                ConcurrentBag<Playlist> playList = new ConcurrentBag<Playlist>(trackOnDelete.Playlists);
                foreach (var pl in playList)
                {
                    pl.Tracks.Remove(trackOnDelete);
                }

                entities.DeleteObject(trackOnDelete);

                entities.SaveChanges();

                #endregion
            }
        }
    }
}

Biraz performans kaybı söz konusu olabilir ancak silme işlemi başarılı bir şekilde gerçekleştirilebilecektir. Özellikle SQL tarafında çalıştırılan sorgulara bakıldığında, aşağıdaki ifadelerin icra edildiği gözlemlenir.

Önce Track ve Playlist tabloları arasındaki çoğa-çok ilişkiyi sağlayan PlaylistTrack tablosundaki ilgili satırlar silinir.

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=1,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=5,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=8,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=12,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=13,@1=3503

Artık sorun yoktur, nitekim Track tablosu ile Playlist arasındaki ilişkiler ortadan kalkmıştır. Buna göre son olarak, Track tablosundan ilgili satırın silinmesi işlemi gerçekleştirilir.

exec sp_executesql N'delete [dbo].[Track]
where ([TrackId] = @0)',N'@0 int',@0=3503

Insert ve Update işlemlerinin incelenmesini de siz değerli okurlarıma bırakıyorum. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ManyToMany_RC.rar (47,16 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Eski Dost ve Entity Framework 4.0

Salı, 9 Şubat 2010 18:28 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere .Net Framework 4.0 ve Visual Studio 2010 sürümlerinin çıkmasına az bir zaman kaldı(Hatta yarın RC sürümü herkese açık olacak). Blog yazısını hazırladığım zaman itibariyle Microsoft' taki güvenilir kaynaklar ve MSDN yazarlarından edinilen bilgilere göre, sürümün Nisan 2010 içerisinde çıkması bekleniyor. Tabi yeni bir sürüm denilince eski sürüm ile aradaki farklılıkları bilmek, nereden nereye gelindiği ve nelerin düzeltildiğini öğrenmek, beklentilerin karşılanıp karşılanmadığına bakmak oldukça önemli. Bende buna istinaden yazımızın ilerleyen kısımlarında, Ado.Net Entity Framework 4.0 ile bir önceki versiyonu arasında, sorgu teknikleri açısından oluşan farkları aktarmaya çalışıyor olacağım. Ado.Net takımının 4.0 sürümündeki hedeflerinden biriside SQL sorgularının daha anlaşılır olmasını sağlamak. Buna ek olarak gereksiz SQL ifadelerinin kullanılmasından uzaklaşılmaya çalışılmış ve hatta önceki Entity Framework sürümünde SQL sorgusuna dönüştürülemeyen LINQ ifadeleri kullanılabilir hale getirilmiş. Tabi daha da fazla fark bulunabilir. Ancak MSDN ve Ado.Net takımının gerek blog yazılarından gerek Webcast' lerinden takip ettiğim kadarı ile aşağıda yer alan 4 önemli farklılık göze çarpmaktadır. Çok doğal olarak söz konusu iyileştirmeler LINQ ifadelerinin SQL sorgularına dönüştürülmesinde devreye giren motor üzerinde yer almaktadır.

Not : Örneklerimizde yer alan LINQ sorguları .Net Framework 3.5 odaklı olan Visual Studio 2008 ve .Net Framework 4.0 odaklı olan Visual Studio 2010 Ultimate Beta 2 ürünleri üzerinde yazılmıştır. Özellikle Ado.Net Entity Framework 4.0 üzerinden daha nihai bir sürüme ulaşılmadığı için ilereyen zamanlarda değişiklikler gözlemlenebilir. LINQ sorgularının icrası sırasında, SQL Server Profiler aracı kullanılarak arka planda yürütülen SQL sorgularının elde edilmesi sağlanmıştır. Ayrıca örnekte Codeplex üzerinden sunulan Chinook veritabanı kullanılmıştır.

 Vaka 1 : Sorgularda In Desteği

Açıklama

Ado.Net Entity Framework 4.0 öncesindeki sürümde SQL tarafında In anahtar kelimesine dönüşebilecek sorgu desteği bulunmamaktadır. 4.0 versiyonunda In' e dönüştürülebilme desteği belirli ölçüde bulunmaktadır. Aşağıdaki LINQ sorgusuna göre, Berlin ve Paris şehirlerinde yer alan müşteriler elde edilmeye çalışılmaktadır. 4.0 öncesi sürümde bu tip bir LINQ sorgusunda yer alan Contains metodunun SQL tarafına dönüştürülemediği görülür. Ancak 4.0 versiyonunda SQL tarafına In olarak aktarılması sağlanmaktadır.

Önceki Versiyon - LINQ Sorgusu

                string[] cityNames = { "Berlin", "Paris" };

                var result = from customer in entites.Customer
                             where cityNames.Contains(customer.City)
                             select customer;

                foreach (var r in result)
                {
                    Console.WriteLine("{0} {1} {2}", r.Email, r.FirstName, r.LastName);
                }

Önceki Versiyon - SQL Sorgusu

Bir SQL sorgusu yürütülememektedir nitekim çalışma zamanında aşağıdaki Exception mesajı alınacaktır.

4.0 - LINQ Sorgusu

                string[] cityNames = { "Berlin", "Paris" };

                var result = from customer in entites.Customers
                             where cityNames.Contains(customer.City)
                             select customer;

                foreach (var r in result)
                {
                    Console.WriteLine("{0} {1} {2}", r.Email, r.FirstName, r.LastName);
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Company] AS [Company],
[Extent1].[Address] AS [Address],
[Extent1].[City] AS [City],
[Extent1].[State] AS [State],
[Extent1].[Country] AS [Country],
[Extent1].[PostalCode] AS [PostalCode],
[Extent1].[Phone] AS [Phone],
[Extent1].[Fax] AS [Fax],
[Extent1].[Email] AS [Email],
[Extent1].[SupportRepId] AS [SupportRepId]
FROM [dbo].[Customer] AS [Extent1]
WHERE [Extent1].[City] IN (N'Berlin',N'Paris')

Vaka 2 - Gruplamada Cast Sorunu

Açıklama

Gruplama uygulanmış olan bir LINQ sorgusunda Count genişletme metodunun(Extension Method) kullanılması halinde bir önceki versiyonda Cast operatörünün SQL sorgusuna dahil edildiği görülür. Ancak 4.0 versiyonunda bu gereksiz durum düzeltilmiştir.

Önceki Versiyon - LINQ Sorgusu

                var result = from track in entites.Track
                             group track by track.Composer into trackGrp
                             select new
                             {
                                 trackGrp.Key,
                                 Count = trackGrp.Count(),
                                 Sum = trackGrp.Sum<Track>(t => t.UnitPrice),
                                 Max = trackGrp.Max<Track>(t => t.UnitPrice),
                                 Min = trackGrp.Min<Track>(t => t.UnitPrice)
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

Önceki Versiyon - SQL Sorgusu

SELECT
1 AS [C1],
[GroupBy1].[K1] AS [Composer],
[GroupBy1].[A1] AS [C2],
[GroupBy1].[A2] AS [C3],
[GroupBy1].[A3] AS [C4],
[GroupBy1].[A4] AS [C5]
FROM ( SELECT
 [Extent1].[Composer] AS [K1],
 COUNT( CAST( 1 AS bit)) AS [A1],
 SUM([Extent1].[UnitPrice]) AS [A2],
 MAX([Extent1].[UnitPrice]) AS [A3],
 MIN([Extent1].[UnitPrice]) AS [A4]
 FROM [dbo].[Track] AS [Extent1]
 GROUP BY [Extent1].[Composer]
)  AS [GroupBy1]

4.0 - LINQ Sorgusu

                var result = from track in entites.Tracks
                             group track by track.Composer into trackGrp
                             select new
                             {
                                 trackGrp.Key,
                                 Count = trackGrp.Count(),
                                 Sum = trackGrp.Sum<Track>(t => t.UnitPrice),
                                 Max = trackGrp.Max<Track>(t => t.UnitPrice),
                                 Min = trackGrp.Min<Track>(t => t.UnitPrice)
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

4.0 - SQL Sorgusu

SELECT
1 AS [C1],
[GroupBy1].[K1] AS [Composer],
[GroupBy1].[A1] AS [C2],
[GroupBy1].[A2] AS [C3],
[GroupBy1].[A3] AS [C4],
[GroupBy1].[A4] AS [C5]
FROM ( SELECT
 [Extent1].[Composer] AS [K1],
 COUNT(1) AS [A1],
 SUM([Extent1].[UnitPrice]) AS [A2],
 MAX([Extent1].[UnitPrice]) AS [A3],
 MIN([Extent1].[UnitPrice]) AS [A4]
 FROM [dbo].[Track] AS [Extent1]
 GROUP BY [Extent1].[Composer]
)  AS [GroupBy1]

Vaka 3 - Join sorgularında Is Null Kontrolü

Açıklama

LINQ tarafında icra edilen aşağıdaki gibi bir Join sorgusunda, bir önceki versiyonda üretilen SQL ifadesinde anahtar alan için IS NULL kontrolü yapıldığı görülmektedir. 4.0 sürümünde ise gereksiz olan bu kontrol SQL tarafına aktarılmamaktadır.

Önceki Versiyon - LINQ Sorgusu

                var result = from artist in entites.Artist
                             join album in entites.Album
                             on artist.ArtistId equals album.Artist.ArtistId
                             select new
                             {
                                 ArtistName = artist.Name,
                                 AlbumTitle = album.Title
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

Önceki Versiyon - SQL Sorgusu

SELECT
1 AS [C1],
[Extent1].[Name] AS [Name],
[Extent2].[Title] AS [Title]
FROM  [dbo].[Artist] AS [Extent1]
INNER JOIN [dbo].[Album] AS [Extent2] ON ([Extent1].[ArtistId] = [Extent2].[ArtistId]) OR (([Extent1].[ArtistId] IS NULL) AND ([Extent2].[ArtistId] IS NULL))

4.0 - LINQ Sorgusu

                var result = from artist in entites.Artists
                             join album in entites.Albums
                             //on artist.ArtistId equals album.Artist.ArtistId
                             on artist.ArtistId equals album.ArtistId //(Zaten yeni sürümde yandaki gibi yazabiliyoruz artık)
                             select new
                             {
                                 ArtistName = artist.Name,
                                 AlbumTitle = album.Title
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[ArtistId] AS [ArtistId],
[Extent1].[Name] AS [Name],
[Extent2].[Title] AS [Title]
FROM  [dbo].[Artist] AS [Extent1]
INNER JOIN [dbo].[Album] AS [Extent2] ON [Extent1].[ArtistId] = [Extent2].[ArtistId]

Vaka 4 - Skip, Take gibi Metod Kullanımlarında Tüm Alanların Sorguya Dahil Edilmesi

Açıklama

Aşağıdaki LINQ sorgusuna göre artistlerin tersten sıralanan adlar listesi ilk 10 satır atlanarak elde edilmektedir. 4.0 öncesi versiyonda bu tip bir LINQ sorgusunun çalıştırılması halinde SQL tarafında oluşturulan Sub SELECT sorgusunda aslında LINQ sorgusuna dahil edilmeyen alanlarında hesaba katıldığı gözlemlenir. 4.0 versiyonunda ise gereksiz alanların SELECT sorgusuna alınmasının önüne geçilmiştir.

Önceki Versiyon - LINQ Sorgusu

                var result = (from artist in entites.Artist
                              orderby artist.Name descending
                              select artist.Name).Skip(10);

                foreach (var r in result)
                {
                    Console.WriteLine(r);
                }

Önceki Versiyon - SQL Sorgusu

SELECT
[Extent1].[Name] AS [Name]
FROM ( SELECT [Extent1].[ArtistId] AS [ArtistId], [Extent1].[Name] AS [Name], row_number() OVER (ORDER BY [Extent1].[Name] DESC) AS [row_number]
 FROM [dbo].[Artist] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Name] DESC

4.0 - LINQ Sorgusu

                var result = (from artist in entites.Artists
                              orderby artist.Name descending
                              select artist.Name).Skip(10);

                foreach (var r in result)
                {
                    Console.WriteLine(r);
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[Name] AS [Name]
FROM ( SELECT [Extent1].[Name] AS [Name], row_number() OVER (ORDER BY [Extent1].[Name] DESC) AS [row_number]
 FROM [dbo].[Artist] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Name] DESC

Görüldüğü üzere Ado.Net Entity Framework 4.0 sürümünde özellikle SQL sorgularına dönüştürme işlemlerinde, motor üzerinde bazı yenilemelerin yapıldığı ve gereksiz işlemlerin önüne geçilmeye çalışıldığı gözlemlenebilmektedir. Açıkçası bu konu ile ilişkili olaraktan çıkacak kitapları merakla bekliyorum. Özellikle Julia Lerman' ın daha önceki Programming Entity Framework kitabını okuyanlar eminim 4.0 için olan versiyonuda sabırsızlıkla ve merakla bekliyordur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Differences.rar (183,69 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 üzerinde geliştirilmiştir. Yarın public olarak yayınlanacak RC sürümü için test edilmemiştir]

Ado.Net Entity Framework 4.0 - Lazy Loading [Beta 2]

Pazartesi, 7 Aralık 2009 11:27 by bsenyurt

Merhaba Arkadaşlar,

Ado.Net Entity Framework' ün en çok eleştirilen yönlerinden birisi, ORM(Object Relational Mapping) ihtiyacını karşılayacak bir alt yapı olarak tasarlanmasına rağmen ORM' in karakteristik özelliklerinden birisi olan Lazy Loading' in(Bir nesnenin ihtiyaç duyulduğu noktada veri kaynağından yüklenmesini-Load hedefleyen bir tasarım kalıbı olarak düşünülebilir) tam manada desteklemiyor olmasıdır. Zaman içerisinde Deferred Loading veya Explicity Lazy Loading gibi isimler ile Ado.Net Entity Framework içerisine bir takım özellikler eklenerek bu eksiklik ortadan kaldırılmaya çalışılsada gerçek manada 4.0 versiyonunun Beta 2 sürümünde, beklenildiği gibi bir iyileştirme olduğunu görmekteyiz. Bu yazımızdaki temel amacımız ise, Ado.Net Entity Framework' ün bir önceki sürümünde durumun ne olduğunu anlamaya çalışıp, 4.0 Beta 2 sürümünde Lazy Loading adına hangi özelliğin getirildiğini(belkide getirilmediğini) ve nasıl kullanıldığını görebilmektir. Örneklerimizde Codeplex üzerinden açık kaynak olarak yayınlanan Chinook veritabanını kullanıyor olacağız. Haydi parmakları sıvayalım Wink Örneklerimizden ilkini Visual Studio 2008 diğerini ise Visual Studio 2010 Ultimate Beta 2 ortamında geliştiriyor olacağız. Ancak her iki örnekte aşağıdaki şekilde görülen tabloları kullanıyor olacak.

Visual Studio 2008 ve Ado.Net Entity Framework V1.0

İlk olarak Console uygulamamızda aşağıdaki gibi bir kod parçası geliştirdiğimizi düşüelim.

using System;
using System.Linq;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities context = new ChinookEntities())
            {
                var albums = from a in context.Album
                             select a;

                foreach (Album albm in albums)
                    Console.WriteLine("{0} [{1}]",albm.Title,albm.Track.Count.ToString());

            }
        }
    }
}

Bu kod parçasında Album listesi elde edildikten sonra Title bilgileri ile birlikte, o anki albüme ait Track sayıları ekrana yazdırılmaktadır. Yani albümler ve bu albümler altında kaç şarkı olduğu bilgisine ulaşmak istediğimizi düşünebilir. Buna göre çalışma zamanı görüntüsü aşağıdaki gibidir.

Hatta SQL Server Profiler aracına bakıldığında söz konusu kod parçası için aşağıdaki sorgunun çalıştırıldığı görülür.

SELECT
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[Title] AS [Title],
[Extent1].[ArtistId] AS [ArtistId]
FROM [dbo].[Album] AS [Extent1]

Bir sorun var mı? Undecided Aslında var. Dikkat edileceği üzere güncel Album Entity nesnesi ile ilişkili olan Track nesneslerinin toplam sayıları(Count) her zaman 0 olarak elde edilmiştir. Bir başka deyişle X Entity nesnesi ile ilişkili olan Y Entity nesnesine dair herhangibir sorgunun işletilmesi söz konusu olmamıştır. Oysaki Lazy Loading kalıbına göre çalışma zamanındaki Album nesne örneklerine ait Track özellikleri üzerinden o anki Track nesne örneğinin herhangibir üyesine erişilmek istendiğinde(Örnekteki Count gibi), SQL tarafındada gerekli sorguların çalıştırılacağı düşünülür. Peki ilk versiyonda yaşanan bu durum üzerine ne yapılmaktaydı? Temel olarak iki basit yöntem ile bu durumu çözüme kavuşturabiliriz. Bunlardan birisi Include metodunun LINQ ifadesinde aşağıdaki gibi kullanılmasıdır.

var albums = from a in context.Album.Include("Track")
                             select a;

Bu durumda çalışma zamanı görüntüsü aşağıdaki gibi olacaktır.

Görüldüğü üzere albümler içerisinde yer alan parçaların toplam sayıları elde edilebilmiştir. Bunun için SQL tarafında da aşağıdaki sorgunun çalıştırıldığı gözlemlenmektedir.

SELECT
[Project1].[AlbumId] AS [AlbumId],
[Project1].[Title] AS [Title],
[Project1].[ArtistId] AS [ArtistId],
[Project1].[C1] AS [C1],
[Project1].[C3] AS [C2],
[Project1].[C2] AS [C3],
[Project1].[TrackId] AS [TrackId],
[Project1].[Name] AS [Name],
[Project1].[MediaTypeId] AS [MediaTypeId],
[Project1].[GenreId] AS [GenreId],
[Project1].[Composer] AS [Composer],
[Project1].[Milliseconds] AS [Milliseconds],
[Project1].[Bytes] AS [Bytes],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[AlbumId1] AS [AlbumId1]
FROM ( SELECT
 [Extent1].[AlbumId] AS [AlbumId],
 [Extent1].[Title] AS [Title],
 [Extent1].[ArtistId] AS [ArtistId],
 1 AS [C1],
 [Extent2].[TrackId] AS [TrackId],
 [Extent2].[Name] AS [Name],
 [Extent2].[AlbumId] AS [AlbumId1],
 [Extent2].[MediaTypeId] AS [MediaTypeId],
 [Extent2].[GenreId] AS [GenreId],
 [Extent2].[Composer] AS [Composer],
 [Extent2].[Milliseconds] AS [Milliseconds],
 [Extent2].[Bytes] AS [Bytes],
 [Extent2].[UnitPrice] AS [UnitPrice],
 CASE WHEN ([Extent2].[TrackId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2],
 CASE WHEN ([Extent2].[TrackId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C3]
 FROM  [dbo].[Album] AS [Extent1]
 LEFT OUTER JOIN [dbo].[Track] AS [Extent2] ON [Extent1].[AlbumId] = [Extent2].[AlbumId]
)  AS [Project1]
ORDER BY [Project1].[AlbumId] ASC, [Project1].[C3] ASC

Dikkat edileceği üzere Album ve Track tablolarının Left Outer Join ile birleştirilmesi söz konusudur. Dahası, sonucun elde edilmesi için tek bir SQL ifadesinin çalıştırılması yeterli olmuştur. Ancak dikkat çeken noktalardan biriside sadece Count değeri ile ilgilenmemize rağmen Track tablosundaki tüm alanların ifadeye dahil edilmesidir. Zaten Count hesabı için SQL tarafında çalıştırılmış bir ifade de bulunmamaktadır. Bunun yerine kod tarafına çekilen Track listesinin toplam değerinin hesaplanması söz konusudur.

Diğer bir teknik ise Load metodunun kullanılmasıdır. Bu amaçla foreach döngüsünde aşağıdaki düzenlemeyi yaptığımızı düşünelim.

foreach (Album albm in albums)
{
   albm.Track.Load();
   Console.WriteLine("{0} [{1}]", albm.Title, albm.Track.Count.ToString());
}

Bu durumda çalışma zamanı görüntüsü Include kullanımındaki ile aynı olacaktır.

SQL Server Profiler aracına bakıldığında ise aşağıdakine benzer bir ekran görüntüsü ile karşılaşılacaktır.

Dikkat edileceği üzere foreach döngüsü içerisinde Load metodunun çağırıldığı her an arka planda bir SQL sorgusunun çalıştırıldığı, yine sadece Count ile ilgilenmemize rağmen tüm Track alanlarının işe katıldığı gözlemlenebilir. Hatta Count ile ilişkili olarak SQL tarafında çalıştırılan bir ifade bulunmamaktadır. Bu değer, kod tarafındaki Track listesinin ilgili Album için elde edilmesinden sonra hesaplanmaktadır. Aslında tam bu noktada, ele alınan senaryo için Include metodunun kullanımının, Load metoduna göre daha efektif olduğunu düşünebiliriz. Nitekim istemci ve SQL sunucusu arasında sadece tek bir sorgusunun çalıştırılması söz konusudur. Diğer yandan foreach döngüsü içerisinde senaryoya göre bazı koşullar sağlandığı takdirde Count özelliğinin kullanılması istendiği durumlarda, Load metodunun tercih edilmesi yoluna gidilebilir. Her neyse...Bakalım Ado.Net Entity Framework 4.0 Beta 2 içerisinde gerçek anlamda Lazy Loading için ne yapılmıştır.

Visual Studio 2010 Beta 2 ve Ado.Net Entity Framework 4.0 Beta 2

Yeni sürümde ObjectContext türevli tip üzerinden uygulanabilen LazyLoadingEnabled isimli bir özellik(Property) bulunmaktadır. Aslında varsayılan olarak Lazy Laoding özelliğinin açık olduğunu söyleyebiliriz. Aynı örneği Visual Studio 2010 Beta 2 üzerinde denediğimizi düşünelim.(Entity adlarının oluşturulması sırasında çoğullaştırma özelliği etkinleştirilmiştir. Bu nedenle Albums ve Tracks isimlendirmeleri söz konusudur)

using System;
using System.Linq;

namespace ReallyLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities context = new ChinookEntities())
            {
                var albumsWithTracks = from a in context.Albums
                                       select a;

                foreach (Album albm in albumsWithTracks)
                {
                    Console.WriteLine("{0} ({1})",albm.Title,albm.Tracks.Count.ToString());
                }
            }
        }
    }
}

Çalışma zamanında aşağıdaki sonuçları elde ederiz.

Dikkat edileceği üzere kod içerisinde herhangibir şey belirtmememize rağmen Album nesneleri ile ilişkili olan Track nesnelerinin Count özelliklerinin değerleri elde edilebilmiştir. Peki arka planda çalışan SQL sorgusu(sorguları)? Wink İşte SQL Server Profiler aracından elde edilen sonuçlar;

Görüldüğü gibi foreach döngüsü içerisinde Track.Count özelliğine gidildiği her noktada bir SQL sorgusu çalıştırılmış ve o anki albüme bağlı olan parça bilgileri çekilmiştir. Dikkat edileceği üzere Count için SQL tarafında yapılmış özel bir sorgulama yoktur. Bu değer kod tarafında elde edilmektedir. Aslında bu çalışma şeklinin Load metodunun kullanımındaki ile benzer olduğunu söyleyebiliriz. Benzer diyorum çünkü Load kullanımında çalıştırılan SQL sorgusu ile buradaki arasında fark vardır. (Bakalım iki şekil arasındaki 9 farkı bulabilecek misiniz? Smile )

Kişisel Not : Aslında bazı blog yazılarında LazyLoadingEnabled özelliğinin true olması halinde bu şekilde çalıştığı gösterilmiştir. İşte sürüm farklılıklarının bir sonucu daha. Bu nedenle Beta 2 üzerinde yazdığımız bu konunun gelecek sürümlerde değişikliğe uğraması söz konusu olabilir.

Şimdi kodumuzda aşağıdaki değişikliği yaptığımızı düşünelim.

using (ChinookEntities context = new ChinookEntities())
{
   context.ContextOptions.LazyLoadingEnabled = false;

Burada LazyLoadingEnabled özelliğine false değer atanması sonucu aşağıdaki sonuçlar ile karşılaşılacaktır.

Beklediğimiz gibi Lazy Loading özelliğini kapattık. Peki ya SQL tarafı? İşte SQL Server Profiler' dan yakalanan SQL sorgusu.

SELECT
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[Title] AS [Title],
[Extent1].[ArtistId] AS [ArtistId]
FROM [dbo].[Album] AS [Extent1]

Sanırım yazımızın başladığı noktadaki sonuçlara döndük ne dersiniz? Wink Özetle Ado.Net Entity Framework 4.0 Beta 2 sürümünde Lazy Loading kabiliyetinin varsayılan olarak açık geldiğini ve istendiğinde Context nesnesinin LazyLoadingEnabled özelliğine atanacak false değeri ile kapatılabileceğini görmüş olduk. Bakalım sürümün ileriki versiyonlarında ne gibi yenilikler olacak. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Before.rar (41,07 kb)

ReallyLazy.rar (98,20 kb)

Ado.Net Entity Framework 4.0 - Stored Procedures ve Complex Types

Pazartesi, 16 Kasım 2009 10:55 by bsenyurt

Merhaba Arkadaşlar,

Ado.Net Entity Framework 4.0 ile birlikte gelecek/gelmekte olan yeniliklerden birisi de, Stored Procedure' lerin dönüş tipi ile alakalıdır. Henüz tam olarak bitirilememiş olan bu özellik şu anki haliyle bir Stored Procedure' den geriye karmaşık bir tipinin(Complex Type) döndürülebilmesine izin vermektedir. Bunun için Designer tarafında destek sunulmaktadır. Aslında önceki Ado.Net Entity Framework sürümünde bir Stored Procedure' ün Entity modeli içerisine eklenmesi sonrasında dönüş kümesinin Scalars veya Entities olarak kullanılması sağlanabilmekteydi. Ancak bir Stored Procedure çıktısının Complex Type bazlı olaraktan kod tarafında ele alınamayışı da de önemli bir eksiklikti. Bakalım 4.0 versiyonunda bu eksikliği gidermek adına neler yapılmış. Yazımızın ilerleyen kısımlarında bu özeliği anlamaya çalışıyor olacağız.

İlk olarak kendimize kobay bir Stored Procedure seçelim WinkBu amaçla Adventure Works veritabanında yer alan uspGetManagerEmployees Stored Procedure' ünü kullanabiliriz. Bu procedure parametre olarak ManagerID isimli int tipinden bir dğer almakta ve aşağıdaki örnek çıktıyı üretmektedir.

Pekala Stored Procedure' ümüzün çıktısı, RecursionLevel,ManagerID,ManagerFirstName, ManagerLastName, EmployeeID, FirstName ve LastName alanlarının karşılıklarını özellik(Property) olarak içeren sınıfa ait nesne örneklerinden oluşan bir nesne kümesi ile ifade edilebilir. Bunu gerçekleştirmek için Visual Studio 2010 Ultimate Beta 2 sürümünde oluşturacağımız Console uygulamamıza Adventure Works veritabanı için yeni bir Ado.Net Entity Data Model öğesi ekleyip sadece uspGetManagerEmployees SP' sini seçtiğimizi düşünelim.

Şimdi adım adım ilerleyerek Complex Type üretimini gerçekleştireceğiz.

  1. İlk olarak Model Browser ve AdventureWorksModel.Store kısmından ilgili Stored Procedure adına sağ tıklanıp Add Function Import seçimi yapılmalıdır.
  2. Seçimin ardından gelen ekranda Get Column Information düğmesine basıldığı takdirde ilgili Stored Procedure' den dönen sonuç kümesi için uygun olan kolon bilgilerinin getirildiği görülür. Burada hem EDM Type hemde SQL Type bilgileri yer almaktadır.
  3. Sonraki adımda ise Create New Complex Type düğmesine tıklanarak Stored Procedure' ün sonuç kümesi için gerekli sınıfın üretilmesi sağlanır.
  4. İstenirse üretilen tip adı Complex kutucuğundan değiştirilir(Biz örneğimizde tip adını ManagerEmployees olarak yeniledik)
  5. Ok tuşuna basılarak işlem tamamlanır.

Aşağıdaki şekilde bahsetmiş olduğumuz adımlar görsel olarak özetlenmektedir.

Şimdi arka planda neler olduğuna bir bakalım. İlk olarak Entity Data Model tarafında söz konusu Stored Procedure için bir Return Type ve Mapping Details kısmında gerekli kolon-özellik eşleştirmelerinin yapıldığı görülür.

Buna ek olarak Return Type için kod tarafında ManagerEmployees isimli ComplexObject türevli bir sınıfın üretildiği gözlemlenir. Class Diagram görüntüsünde bu durum açık bir şekilde izlenebilmektedir.

Çok doğal olarak söz konusu Stored Procedure' ün kod içerisinde kullanılabilmesi için gerekli özelliğin de AdventureWorksEntities Context tipi içerisine eklendiği görülebilir.

Dikkat edileceği üzere uspGetManagerEmployees isimli özelliğin(Property) dönüşü ObjectResult<ManagerEmployees> tipindendir. Bu tip IEnumerable<T> ve IEnumerable arayüzlerini(Interface) implemente ettiğinden LINQ sorgularında kaynak veri olarak kullanılabilir.

Dilerseniz birde söz konusu Stored Procedure' e ait çıktıyı örnek kod parçasında değerlendirmeye çalışalım. Bunun için aşağıdaki gibi basit bir kodlama yapmamız yeterli olacaktır.

using System;
using System.Data.Objects;
using System.Linq;

namespace SPAndComplexType
{
    class Program
    {
        static void Main(string[] args)
        {
            using (AdventureWorksEntities context = new AdventureWorksEntities())
            {
                ObjectResult<ManagerEmployees> resultSet=context.uspGetManagerEmployees(16);

                var subResult = from me in resultSet
                                where me.FirstName[0] == 'M'
                                select me;

                foreach (var managerEmployee in subResult)
                {
                    Console.WriteLine("{0} {1} {2} {3} {4} {5} {6}",
                        managerEmployee.EmployeeID.ToString(),
                        managerEmployee.FirstName,
                        managerEmployee.LastName,
                        managerEmployee.ManagerFirstName,
                        managerEmployee.ManagerID,
                        managerEmployee.ManagerLastName,
                        managerEmployee.RecursionLevel
                        );
                }
            }
        }
    }
}

Örnek kod parçasında uspGetManagerEmployess özelliği çağırılmış ve ManagerID değeri 16 olan Employee listesinin getirilmesi sağlanmıştır. Bu işlemin arkasından da elde edilen sonuç kümesi üzerinden basit bir LINQ sorgusu çalıştırılmış ve FirstName alanının ilk harfi M olanların listelenmesi sağlanmıştır. Hemen bir hatırlatma yapalım; SQL tarafındaki uspGetManagerEmployees Stored Procedure' ünün çalıştırılması context nesne örneği üzerinden uspGetManagerEmployess özelliğinin çağırılması ile birlikte gerçekleşmektedir. Son olarak uygulamayı çalıştırdığımızda aşağıdaki çıktıyı elde ettiğimizi görürüz.

Böylece geldik bir yazımızın daha sonuna. Ado.Net Entity Framework 4.0 ile ilişkili yenilikleri öğrendikçe sizlerede aktarmaya çalışıyor olacağım. Bu konuda ilk elden bilgi almak isteyen arkadaşlarımızın Ado.Net Team Blog' unu mutlaka takip etmesini öneririm. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SPAndComplexType.rar (36,40 kb)