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

Entity Framework, Data Services, C# 4.0, Excel ve Komple Bir Uygulama

Perşembe, 26 Ağustos 2010 16:50 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere bir süre önce Visual Studio 2010 ve .Net Framework ürünlerinin RTM sürümleri yayınlandı. Her iki ürünüde sizlerle birlikte , Microsoft PDC 2008 konferanslarından bu yana gerek yazılarımızla, gerek görsel derslerimizle incelemeye çalışıyoruz. Özellikle .Net Framework 4.0 açısından baktığımızda alet, edevat çantamızın dop dolu olduğunun eminimki hepimiz farkındayız. Paralel programlamadan tutun, WCF Eco System'e, C# 4.0 ile birlikte gelen yeniliklerden, WF 4.0 tarafına kadar pek çok noktada ek kabiliyetler, iyileştirmeler ve daha fazlası söz konusu. Aslında sizde benim gibi zaman zaman bu alet kutusu içerisindeki parçalardan bir kısmını alıp, örnek bir uygulamada kullanmaya çalışarak vaktinizi değerlendirmeye ve dolayısıyla offlama sorununa çare bulmaya çalışıyor olabilirsiniz. İşte bende bu düşünceler eşliğinde, havanın çok güzel olduğu şu bahar aylarında dışarıya çıkıp dolaşma şansını bulmama rağmen, evde kalıp örnek bir uygulama geliştirmeye karar verdim. İşte bu yazımız için alet çantası içinden seçtiklerimiz.

  • Codeplex üzerinden yayınlanan bir adet Chinook veritabanı Wink,
  • Ado.Net Entity Framework 4.0,
  • WCF Data Services,
  • C# 4.0 Optional, Named Parameters,
  • Microsoft.Office.Interop.Excel,
  • ve tabiki Visual Studio 2010

Gelelim alet çantasından çıkarttığımız araçlar ile yapmak istediğimize...

Öncelikli olarak Chinook veritabanının içeriğini Ado.Net Entity Framework üzerinden dış dünyaya sunan bir WCF Data Service örneğimiz olduğunu düşünebiliriz. Bu servisin sunduğu verinin istemcisi olan uygulama ise, talep ettiği içeriği alarak bir Excel uygulamas içersinde yayınlıyor olacak. Dolayısıyla Client uygulama tarafında Microsoft.Office.Interop.Excel.dll Assembly' ının referans edilmesi gerektiğini şimdiden söyleyebiliriz. Bu sayede Excel API' si yönetimli kod tarafından rahatlıkla konuşabiliyor olacağız. Diğer taraftan istemci uygulamada C# 4.0 ile birlikte gelen ve Office uygulamaları ile olan etkileşimde büyük avantajlar sağlayan Named ve Optional Parameters kavramlarının ele alınacağını da ifade edebiliriz. İlk etapta hedefimiz örnek olarak Track tablosundan, istemcinin belirttiği AlbumId değerine sahip olan satırları almak ve bunları örnek Excel uygulamasında açılacak Workbook üzerindeki bir Sheet içerisinde göstermek olacak. Projeye ait Solution içeriği herşey tamamlandığında aşağıdaki gibi olacaktır.

İlk olarak ChinookEntityLayer isimli Class Library projesinin geliştirilmesi söz konusudur. Bu Library içerisine eklenen Ado.Net Entity Data Model içerisine, Chinook veritabanında yer alan tüm tabloları ekleyebiliriz. Örneğimizde çok basit bir operasyonu göz önüne alıyor olsakta, sizlerin bu örnekten ilham alarak farklı sorguları da işin içerisine katacağınıza eminim Wink ChinookServices isimli WCF Service Application tipinden olan uygulama, içerdiği WCF Data Service sayesinde Chinook veritabanına ait Entity koleksiyonlarını dış ortama sunmaktadır. Dolayısıyla bu uygulama, ChinookEntityLayer isimli sınıf kütüphanesini de referans etmelidir. Diğer yandan önemli olan noktalardan birisi de, Entity Context nesnesi tarafından kullanılan Connection String bilgisidir. ChinookEntityLayer içerisindeki app.config dosyasına eklenen Connection String içeriğinin aslında ChinookServices isimli WCF Service uygulamasının web.config dosyası içerisinde olması gerekmektedir. Çünkü çalışma zamanında oluşturulan Context nesne örneğinin yer aldığı proje burasıdır ve bu sebepten çalışma zamanı Connection String bilgisini Web.config içerisinde arayacaktyır.

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="ChinookEntities" connectionString="metadata=res://*/ChinookModel.csdl| res://*/ChinookModel.ssdl|res://*/ChinookModel.msl; provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.; Initial Catalog=Chinook;Integrated Security=True; MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>
</configuration>

WCF Service uygulaması içerisinde yer alan ChinookDataService isimli WCF Data Service tipinden olan örneğin kod içeriği ise aşağıdaki gibidir.

using System.Data.Services;
using System.Data.Services.Common;
using ChinookEntityLayer;

namespace ChinookServices
{
    public class ChinookDataService
        : DataService<ChinookEntities>
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

Buna göre Chinook veritabanı içerisindeki Entity koleksiyonlarının tamamı sadece okunabilir olacak şekilde dış dünyaya sunulmaktadır.

Artık istemci tarafının geliştirilmesine başlanabilir. Console uygulaması tipinden olan istemci tarafına(Neden Console şeklinde tasarladığımı lütfen sormayın Smile) öncelikle ChinookDataService isimli WCF Data Service örneğinin referans edilmesi gerekmektedir. Söz konusu servis ile istemci uygulama aynı Solution içerisinde yer aldığında Add Service Reference seçeneğini aşağıdaki şekilde görüldüğü gibi kullanmak yeterlidir.(Hatırlanacağı üzere Astoria kod adlı Ado.Net Data Service' lerin Visual Studio 2008 üzerinden kullanılan sürümlerinde, Add Service Reference seçeneği kullanılamamktaydı. Bunun için datasvcutil aracından yararlanmamız gerekiyordu. Tabiki, Data Service için Add Service Reference desteği Visual Studio 2010 içerisinde mevcut)

Artık istemci uygulama geliştirilmeye başlanabilir ki belki de işin en heyacanlı kısmı burasıdır Wink İşte Console uygulamamız ait kod içeriğimiz.

using System;
using System.Linq;
using ClientApp.ChinookDataServiceReference;
using Excel = Microsoft.Office.Interop.Excel;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // WCF Data Service örneğini kullanabileceğimi şekilde ChinookEntities nesne örneği oluşturulur.
            // URI Satırı söz konusu WCF Data Service örneğini işaret etmektedir.
            ChinookEntities entities = new ChinookEntities(new Uri("http://localhost:4071/ChinookDataService.svc/"));
           
            // Kullanıcıdan AlbumId bilgisi istenir
            Console.WriteLine("Album Id?");
            int albumID;
            if(!Int32.TryParse(Console.ReadLine(), out albumID)) //Eğer dönüşüm başarılı değilse 1 numaralı AlbumId değeri baz alınır
                albumID=1;

            // Sorgu cümlesi
            // AlbumId değerine göre Track örnekleri çekilir. Bu işlem sırasında Genre Entity örneklerine de ihtiyacımız olduğundan Expand metodu ile gerekli çağrı yapılır
            var result = from t in entities.Tracks.Expand("Genre")
                         where t.AlbumId == albumID
                         orderby t.Name
                         select t;

            // Bir Excel Application nesnesi örneklenir.
            Excel.Application excApp = new Excel.Application();
            excApp.Visible = true; // Excel uygulamasının görünebilir olacağı belirtilir
            excApp.Workbooks.Add(); // Yeni bir Workbook eklenir

            // Sütun başlıkları set edilmeye başlanır
            excApp.get_Range("A1").Value = "Track Name";
            excApp.get_Range("B1").Value = "Genre";
            excApp.get_Range("C1").Value = "Composer";
            excApp.get_Range("D1").Value = "Milliseconds";
            // Etkin olan Sheet adı belirlenir
            excApp.ActiveSheet.Name = "Track List for Album Id 1";

            // Elde edilen veri kümesindeki sonuçlar Sheet içerisindeki ilgili hücrelere yazdırlır
            int rowNumber = 2;
            foreach (var t in result)
            {
                excApp.get_Range(String.Format("A{0}", rowNumber.ToString())).Value = t.Name;
                excApp.get_Range(String.Format("B{0}", rowNumber.ToString())).Value = t.Genre.Name;
                excApp.get_Range(String.Format("C{0}", rowNumber.ToString())).Value = t.Composer;
                excApp.get_Range(String.Format("D{0}", rowNumber.ToString())).Value = t.Milliseconds;
                rowNumber++;
            }

            // Tüm sütunların uzunlukları içeriklerine göre otomatik olarak genişletilir
            for (int i = 1; i < 5; i++)
            {
                excApp.Columns[i].AutoFit();
            }
        }
    }
}

Console uygulaması kullanıcıdan bir AlbumId değeri istemektedir. Söz konusu AlbumId değerine göre WCF Data Service örneğine bir talep gönderilir ve bu talebin karşılığında dönen sonuç kümesi değerlendirilerek Excel içerisine alınması sağlanır. Uygulamanın çalışma zamanına ait örnek ekran çıktılarından birisi aşağıdaki gibidir.

Ta taaaaaa!!! Laughing Bence güzel bir örnek oldu. Ancak daha da geliştirilmesi lazım. Her şeyden önce Console tipinde olan istemci uygulamadan kurtulmak ve görsel arayüze sahip bir örnek üzerinden ilermelek daha yararlı olacaktır. Bu size bir ödev olabilir mesela. Yazımızı sonlandırmadan önce benim sizlere bir kaç sorum olacak;

  • Örnekte C# 4.0 ile birlikte gelen hangi yeni özellikler kullanılmıştır? (Daha önceki yazılarımızda değindik)
  • WCF Data Service örneğine doğru gönderilen sorgu sonucunda elde edilen içeriğe göre, Excel üzerinde oluşturulacak sütun adları dinamik olarak belirlenebilir mi?
  • WCF Data Service içerisinde kullanılan DataServiceProtocolVersion.V2 değeri ne anlama gelmektedir? (Daha önceki yazılarımızda değindik)
  • Aynı örnek için şu tip bir sorguyu deneyip Excel çıktısını almaya çalışabilir misiniz? "Composer bazlı Track sayıları?"
  • Uygulamanın sonunda Excel tablosunun otomatik olarak kayıt edilmesini sağlayabilir misiniz?
  • WCF Eco System içerisinde yer alan Data Service dışındaki türler nelerdir? Bu servis türleri hangi amaçlarla kullanılmaktadır? (Daha önceki yazılarımızda değindik)
  • İstemci uygulamaya ait exe dosyasının çıkartıldığı yerde Excel ile ilişkili bir Assembly bulunmamaktadır. Neden olduğunu bulup açıklayınız? (Daha önceki yazılarımızda değindik)

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

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

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]

Screencast - WCF RIA Services, OData, Excel PowerPivot

Salı, 23 Mart 2010 09:05 by bsenyurt

Merhaba Arkadaşlar,

Yeni bir maceraya daha hazır mısınız? Evet dediğinizi duyar gibiyim. Bu sefer kobay haline getirdiğimiz Chinook veritabanına yine Entity Framework 4.0 üzerinden bir WCF RIA Service yardımıyla erişiyoruz. Ancak Domain Service tipini üretirken OData(Open Data Protocol) desteği vereceğini belirtiyoruz. Bu durumda servisimizin çıktı olarak ürettiği Feed içeriğinin OData standartlarını destekleyen herhangibir ürün tarafından kullanılabileceğini belirtmiş oluyoruz. İstemci tarafında ise Excel 2010 Beta ürününe bir AddIn olarak gelen PowerPivot aracından yararlanıyoruz. Buna göre servisin sunduğu veri içeriğini, sadece bir iki hareketle Excel üzerinde kullanılabilir halde çekmiş oluyoruz. Söz konusu fonksiyonellik sayesinde veriyi ister ızgara görünümünde bırakabilir ister detaylı grafiklerini çıkartabiliriz. İşin güzel yanı, servisi dünyanın herhangibir noktasındaki bir sunucu üzerinde host edebilecek olmamız. Dolayısıyla Excel PowerPivot aracı söz konusu servise bağlanabildiği sürece verinin en güncel halini çekebiliyor olacak. E ne duruyorsunuz? NedirTv? sponsorluğundaki görsel dersimizi izlemeye buyrun.

Süre : 14:52

Boyut : 24.5 Mb

Download etmek veya izlemek için