Merhaba,
Bu yazıda sizlere WCF servis katmanında, Entity Framework ile birlikte gelen Self Tracking Entity kullanımına dair yaptığım araştırmalar, geliştirdiğim örnekler ve karşılaştığım problemlerden bahsetmeye çalışacağım.
Bildiğiniz gibi, servis tabanlı projelerde birden fazla katman bulunmakta, client uygulama, iş mantığı ve bu iş mantıkları sonucu üretilen verilerin tamamı ayrı makinalarda çalışmakta ve yer almaktadır. Bunun sonucu olarak, bir katman diğerine ait kaynakları direkt olarak kullanamamakta ve iletişim kurallarının gereği olan bazı engellerle karşılaşmaktadır.
Bu problemi; veri katmanında Entity Framework kullanan bir örnek ile açıklayalım, ve Self Tracking Entity yapısının bu probleme nasıl bir çözüm getirdiğini görelim.

Şekil 1 – Katmanlı Mimari
Veri katmanında kullanacağımız örnek için, daha önceden oluşturmuş olduğumuz tabloları kullanacağız. Bu tabloları, projemize yeni bir Entity Framework modeli (EDMX) ekleyerek projemize taşıyalım.

Şekil 2 – Veri Modeli
İlk oluşan EDMX modelinin özelliklerinde, Code Generation Strategy = Default olarak bulunmaktadır. Bunun anlamı; EDMX modelinden faydalanılarak oluşturulan entity yapısı, Entity Framework’ün sağladığı default code generator ile oluşturulmuştur. Eğer default bir kod üretilmesini istemiyor isek, bu seçeneği None olarak işaretlememiz gerekir.

Şekil 3 – Model Properties
Öncelikle, default oluşan entity yapısına kısaca bir göz atalım ve bu entitylerin nerede bulunduğuna dikkat edelim. Model dosyası altında bulunan *.Designer.cs dosyasını açarsak, burada hem default ObjectContext, hem de default entity yapısı için oluşturulan kodları görebiliriz. Burada, entity ve context nesnesi, EDMX modeli ile sıkı sıkıya bağlıdır. Eğer biz bu entityleri Presentation katmanında kullanmak istersek, bu mümkün olmayacaktır. Presentation katmanına bu entityleri taşımak için, mecburen EDMX modelini de Presentation katmanına taşımamız gerekir ki, bu da çok katmanlı mimari bakış açısının istemediği, veri erişim ve iş mantığı yapılarının client makinalara gitmesi anlamına gelecektir.
Bu sebeple, Microsoft ve diğer üçüncü partiler tarafından T4 code generation templateleri kullanılarak, değişik amaçlara yönelik entity ve context templateleri oluşturulmuştur. Bu templatelerden bir tanesi de, Microsoft tarafından sağlanan Self Tracking Entity templateidir. Self Tracking Entity templatei tarafından oluşturulan entity nesneleri, WCF servisleri üzerinden her iki yöne hareket edebilen, ekranlara bind edilebilen, ekranlar üzerinde yapılan değişiklikleri kendi üzerinde takip edip, veri katmanında ise bu değişikleri veritabanına yansıtabilen yapılardır. Microsoft Patterns & Practices grubunun mimari tavsiye dökümanlarında tavsiye edilen DTO (Data Transformation Object) yapısı yerine, tamamen ekran hizmetlerine yönelik tasarlanan servislerde Self Tracking Entity yapısı kullanılması, geliştirme hızını artıracak ve yazılan kod miktarını azaltacaktır. Ancak unutulmamalıdır ki, bu yapı tamamen .NET teknolojileri ile geliştirilen ortamlarda çalışması amacıyla geliştirilmiştir, Java ve diğer teknolojiler ile geliştirilen uygulamalar, bu servisler ve entitylerden faydalanamayacaktır.
Self Tracking Entity yapısının kullanımı ve Servis Tabanlı Mimariye uygun hale getirilmesi
Self Tracking Entity yapısını kullanabilmek için, EDMX modeli üzerinde sağ tıklayıp, Add Code Generation Item menüsünden, ADO.NET Self-Tracking Entity Generator seçeneği ile T4 template oluşturulur.


Şekil 4 – Add Code Generation Item
T4 templatelerinin oluşması ile birlikte, EDMX model properties üzerindeki Code Generation Strategy özelliğinin otomatik olarak None değerine set edildiğini kontrol edebilirsiniz. Ayrıca model dosyasına bağlı *.Designer.cs dosyasının içerisinde de context ve entity tanımı içeren herhangi bir kod kalmadığını da görebilirsiniz.
Oluşan T4 templatelerini ve ürettiği kodların yapısını incelersek, Model.tt dosyası altında Self Tracking Entity yapıları ve bu entitylerin kullanacağı yardımcı interface, class ve extension methodların bulunduğunu; Model.Context.tt dosyası altında ise, bu entityleri yönetecek ObjectContext ve yardımcı extension methodların bulunduğunu görebiliriz.
Ancak, presentation katmanında bu entityleri bu haliyle kullanmak istersek, veri katmanına bir bağımlılık oluşturmak zorunda kalırız, ve bu bağımlılık bizim arzu ettiğimiz mimari yapıyı bozacaktır. Bu sebeple, Self Tracking Entityleri, hem presentation, hem servis, hem de veri katmanının erişebileceği ortak bir kütüphaneye taşımamız gerekmektedir. Bu da, örnek proje mimarisinde de görebileceğiniz Shared isimli kütüphanedir. Shared kütüphanesi, WCF servislerinin ve presentation katmanının kullanacağı service contract ve data contract yapılarının bulunacağı kütüphanedir.
Self Tracking Entity yapılarını Shared projesine taşımak için, Model.tt isimli T4 templateini o projeye taşımamızın dışında içerisindeki EDMX dosyasının yolunu belirten kısmı aşağıdaki şekilde değiştirmek yeterli olacaktır.

Şekil 5 – EDMX path
Self Tracking Entity ile kayıt işlemleri
Örneğimizi geliştirmeye Person nesnesi üzerinden başlayalım. Person nesnesini, eğer veritabanında yok ise veritabanına ekleyecek, eğer veritabanında mevcut ise yapılan değişiklikleri veritabanında güncelleyecek bir servis methodu yazalım.

Şekil 6 – SavePerson methodu
İlk olarak, Person nesnesinin tamamının değil, sadece primitive propertylerinin set edilerek ekrandan gönderildiğini varsayalım. Navigation propery ve collection propertyleri yazının ilerleyen kısımlarında inceleyeceğiz. SavePerson methodu için bu seviyedeki bir implementasyon aşağıdaki şekilde olacaktır.

Şekil 7 – SavePerson implementasyonu
Person nesnesi üzerinde ObjectChangeTracker tipinde ChangeTracker isimli bir property bulunmaktadır. Bu property üzerinde, nesnenin o anki durumu, hangi özelliklerinin değişikliğe uğradığı ve eski özelliklerinin değerleri bulunmaktadır. Bizim ilk gönderdiğimiz nesne, ekran tarafında oluşturulan yeni bir nesne olduğundan dolayı, statüsü Added olacaktır. ObjectState enum tipindedir ve alabileceği diğer değerler, Modified, Deleted ve Unchanged değerleridir.
Yukarıdaki implementasyonda kullanılan ApplyChanges methodu, Self Tracking Entity templatei ile gelmiş olan bir extension methoddur. Amacı, parametre olarak geçilen nesne üzerindeki değişiklikleri ChangeTracker vasıtasıyla tespit edip, context üzerindeki nesneye bu değişiklikleri yansıtmaktır. SaveChanges methodunun çağrılmasıyla birlikte bu değişiklikler veritabanına yansıtılır, ve eğer veritabanından bu nesneye aktarılacak özellikler varsa (autoincrement id gibi) aktarım yapılır. Ancak ilk gönderdiğimiz Person nesnesi, hala Added statüsündedir ve değişiklik yapılan alanlara dair bilgiler hala üzerinde mevcuttur. Eğer kayıt esnasında bir problem oluşmadı ise, artık bu nesne veritabanındaki nesne ile birebir eşittir, dolayısıyla durumunun Unchanged olması ve değişiklik takibiyle alakalı tüm özelliklerin temizlenmesi gerekmektedir. Kayıt sonrası yapılacak tüm bu temizlik işlemleri, yine Self Tracking Entity templatei ile gelen bir extension methodu olan AcceptChanges vasıtası ile yapılır. Bu ilk implementasyon ile kayıt işlemleri tamamlanır.
Servis tarafında nesne üzerinde oluşacak değişikliklerin Presentation katmanına yansıtılması
Person örneğinden gidersek, nesnemiz ekran tarafında Added statüsünde oluşturuldu, servis katmanı vasıtasıyla başka bir ortama taşındı ve veritabanına kaydedildi. Kayıt sonrasında ise, nesne üzerinde PersonRef isimli alana veritabanında üretilen bir numara aktarıldı, ve nesne statüsü Unchanged olarak değiştirildi. Tüm bu değişikliklerin ekran tarafına tekrar bildirilmesi gerekmektedir. Burada akla gelen ilk çözüm, servis methodunun Person nesnesinin son halini return etmesi, ekran tarafındaki nesneye de bu değişikliklerin yansıtılması olacaktır. Ancak bu örnekteki Person nesnesi basit bir nesne olabilir, gerçek uygulamalarda ise Person nesnesi kadar basit nesnelerle karşılaşmanızın zor olacağına, bu değişikliklerin yansıtılmasının da keza bir o kadar zor olacağına bahse girebilirim. İşte burada karşımıza, C# dilinin güzelliklerinden ref keywordu çıkıyor. Bir methoda parametre geçilen nesne, eğer ref vasıtası ile geçilir ise, method içerisinde o nesne üzerinde yapılacak her değişiklik asıl nesne üzerinde yapılacaktır. Servis ortamında bunun gerçekleşmesi biraz zor gibi gözükse de, WCF bu özelliği desteklemektedir.
Bu durumda bir önceki yazdığımız kodda yapılacak tek değişiklik, hem service interface, hem de implementasyon tarafındaki method imzasını aşağıdaki şekilde olduğu gibi ref keywordunu eklemek olacaktır. Böylelikle SaveChanges ve AcceptChanges methodlarını çağırdığımızda, nesne üzerinde gerçekleşen değişiklikler, ekran tarafındaki nesneye de direkt olarak yansıyacaktır. (Eğer binding kullanıldı ise ekran üzerinde de görülebilecektir)

Şekil 8 – ref kullanımı
Navigation Property bulunduran bir Self Tracking Entity ile kayıt işlemleri
Örneğimiz ile devam edelim. Ekran üzerinde, cinsiyet bilgisinin seçilebileceği bir combobox oluşturalım ve bu comboboxa, servis vasıtasıyla getirilecek olan Gender listesini dolduralım. Comboboxtan bir Gender seçilmesi ile birlikte de, bu Gender nesnesini Person nesnesi üzerindeki Gender nesnesine set edelim. Bu haliyle de Person nesnemizi kaydedelim.
WPF teknolojisi arkasındaki binding mekanizması, ekran ile arkasında kullanılan modeli birbirine bağlayarak, ekran üzerinde yapılan bir değişikliği anında modele, model üzerinde yapılan bir değişikliği de anında ekrana yansıtarak, hem bu işlemleri yapabilmek için yazılacak kod miktarını sıfıra indirmekte, hem de ekran-model arasındaki senkronizasyonu mükemmel derecede sağlamaktadır. Biz de örneğimizde, binding mekanizmasından faydalanıyor olacağız.
İlk olarak, combobox’a bağlayacağımız Gender listesini getiren servis methodunu ve implementasyonunu aşağıdaki şekilde oluşturalım.

Şekil 9 – Gender listesi
ObservableCollection nesnesi, WPF ekranlarına liste tipindeki nesneleri bind etmek için kullanılan bir yapıdır. Burada, gelen listeyi direkt olarak ekrana bind etmek amacıyla ObservableCollection kullanıyoruz.
Listenin comboboxa bind edilmesi ile birlikte, combobox içerisinde cinsiyet bilgileri listelenecektir. Burada seçim yapılan cinsiyet bilgisinin de aynı şekilde, Person nesnesinin Gender tipine bind edilmesi gerekir. Bunun için combobox üzerinde aşağıdakine benzer bir XAML kodu yazılması gerekmektedir.

Şekil 10 – Combobox bind işlemi
Comboboxta listelenen ve seçim yapıldığında Person nesnesi üzerine set edilen Gender nesnelerini debug esnasında inceleyelim. Bu nesnelerin tamamının Unchanged olduğuna dikkat etmek gerekir. Böylelikle, Person nesnesi üzerindeki Gender özelliği de Unchanged olarak servis methoduna geçecek, dolayısıyla veritabanına herhangi bir Gender bilgisi tekrar eklenmeyecektir. Eğer Gender entitysi, yanlışlıkla Added statüsüyle gönderilmiş olsaydı, veritabanına yeni bir primary key ile bir gender entity’si eklenmeye çalışılacaktı (autoincrement id olmasından dolayı).
Bu haliyle uygulamayı test edersek, başarılı bir şekilde çalıştığını göreceğiz. Ancak, uygulamayı birkaç kez aynı şekilde kayıt yapması için tetiklediğimizde, bir süre sonra “AcceptChanges cannot continue because the object’s key values conflict with another object in the ObjectStateManager” şekilde bir exception aldığımızı göreceğiz. Bunun sebebi, kayıt için gönderdiğimiz entity grafiği üzerinde, aynı primary keye sahip birden fazla nesne bulunmasıdır. Buna sebep olan ise, Gender nesnesinden Person nesnesine erişmeyi sağlayan geriye doğru olan referanstır. Eğer bu şekilde bir erişime ihtiyacımız yok ise, EDMX modeli üzerindeki bu geriye doğru olan referansı silerek bu problemi çözebiliriz. (Bu hataya sebep olan durumlar hakkında detaylı bilgi ve diğer çözüm yolları için Diego B Vega’nın yazmış olduğu makaleyi bu linkten okuyabilirsiniz: http://blogs.msdn.com/b/diego/archive/2010/10/06/self-tracking-entities-applychanges-and-duplicate-entities.aspx )

Şekil 11 – İlk oluşturulan model

Şekil 12 – Modelin Person referansı silindikten sonraki durumu
Modeli bu şekilde oluşturduktan sonra, T4 templatelerini ilgili değişiklikleri koda yansıtması için tekrar çalıştırıp, uygulamanın sorunsuz bir şekilde çalıştığını test edebilirsiniz.
Collection Property bulunduran bir Self Tracking Entity ile kayıt işlemleri
Örneğimize, Person nesnesi üzerinde bulunan Address bilgilerini girmek ve kaydetmek suretiyle devam edelim.
Ekran üzerinden Address girişini bir grid vasıtası ile yapalım. Öncelikle ilk dikkat etmemiz gereken, Person üzerindeki Address listesinin bir TrackableCollection olduğudur. TrackableCollection tipinin detaylarını incelediğimizde ise ObservableCollection tipinden miras aldığını göreceğiz. Dolayısı ile Person üzerindeki Address listesini WPF ekranındaki bir gride rahatlıkla bind edebilir ve ekran üzerinde yapılacak değişiklikleri takip edebiliriz. Grid üzerine bind etme örneğini aşağıda görebilirsiniz.

Şekil 13 – Grid bind işlemi
İlk oluşturduğumuz Person nesnesi üzerinde herhangi bir Address bilgisi olmadığından dolayı grid ilk aşamada boş olarak gelecektir. Ekran üzerine “Yeni Adres Ekle” isimli bir buton ekleyip, bu butonun Address collectionuna yeni bir Address nesnesi eklemesini sağlayarak, Person üzerindeki listeye yeni bir Address satırı eklendiğini hem ekranda, hem de nesne üzerinde görebiliriz.

Şekil 14 – Collectiona yeni nesne eklenmesi
Yeni eklediğimiz Address nesnesi üzerindeki bilgileri değiştirip, SavePerson methodunu tekrar çağıralım. Address nesnesi veritabanına eklenecek, veritabanından alabileceği değerler ekranda güncellenecektir. Ancak nesneyi biraz daha incelersek, statüsünün kaydedilmesine rağmen hala Added olduğunu göreceğiz. Oysa ki kayıt işlemi sonrasında, statü ve diğer değişiklik takibi ile ilgili bilgileri temizlemek için Person nesnesi için AcceptChanges methodunu çağırmıştık. AcceptChanges methodunu derinlemesine incelersek, bu temizlik işlemini sadece çağrıldığı nesne için yaptığını göreceğiz. Dolayısıyla bu nesne altında bulunan liste tipindeki diğer nesnelerin her biri için AcceptChanges methodunu çağırmamız gerekir.

Şekil 15 – Address listesi için AcceptChanges methodunun çağrımı
Bu değişiklikle beraber, uygulamamızın sorunsuz bir şekilde çalıştığını test edebiliriz. Bu örnek için çalışan bu kod parçası, Person ya da Address nesnesi üzerine yeni collection propertyler eklendiğinde de çalışacak mıdır? Cevap hayır, bu durumda bu entity ağacındaki tüm nesnelerin collectionlarını tek tek foreach ile dönerek, AcceptChanges methodunu çağırmamız gerekecektir. Bu tip bir geliştirmenin de, karmaşık entity ağaçlarında aynı şekilde karmaşık kodlar yazılmasına sebep olacağı aşikardır.
İşte burada karşımıza Iterator ve Visitor patternleri çıkmaktadır. Iterator Pattern, bir yapının alt parçaları üzerinde gezmeyi sağlayan patterndir. Visitor Pattern ise, gezilen bu parçaların üzerinde işlemler yapılmasını sağlayan patterndir. Bu iki patterni kullarak, tüm Person nesnesi üzerindeki alt nesneleri gezen ve AcceptChanges methodunu çağıran bir yapı geliştirebiliriz. (Buna benzer bir yapının nasıl geliştirileceğine dair bilgiye, Jeff Derstadt’ın yazmış olduğu bu makaleden ulaşabilirsiniz: http://blogs.msdn.com/b/adonet/archive/2010/06/02/working-with-sets-of-self-tracking-entities.aspx . Makalenin en altında bulunan Iterator.tt isimli T4 templateini gerekli değişiklikleri yaparak projenize dahil edebilirsiniz)
Iterator yapının geliştirilmesinden sonra, tüm nesneler için AcceptChanges methodunu çağıracak, AcceptAllChanges isimli bir method aşağıdaki şekilde geliştirilebilir. Böylelikle SavePerson methodundaki foreach döngüsü tamamen kaldırılabilir, AcceptChanges methodu yerine de bu yeni method çağrılarak kodumuz gelişmeye açık bir hale getirilebilir.

Şekil 16 – AcceptAllChanges methodu

Şekil 17 – SavePerson methodunun son hali
Self Tracking Entity içerisindeki Collection Property’e ait bir nesnenin değiştirilmesi
Örneğimizin çalışan son hali ile, Person nesnesi içerisinde birden fazla Address nesnesi ekleyelim ve kaydedelim. Yeni eklediğimiz nesnelerin tamamının, servis methoduna Added statüsü ile ulaşacağını ve kayıt işleminden sonra AcceptAllChanges methodunun çağrılması ile birlikte Unchanged statüsü ile yaşamlarına devam edeceklerini hatırlayalım.
Eklediğimiz bir Address nesnesi üzerinde değişiklik yapalım ve debug esnasında statüsünü kontrol edelim. Nesnenin statüsünün Modified olarak değiştiğini göreceğiz. Bunu sağlayan yapının arkasında, INotifyPropertyChanged interfacei ve binding mekanizması bulunmaktadır. Ekran üzerinde değişikliği yaptığımız anda, binding vasıtasi ile bu değişiklik nesnenin ilgili propertysine yansıtılmış, bu propertynin değiştiğine dair bir bildirim mekanizması ise OnPropertyChanged eventi vasıtasıyla devreye girmiştir. Bu eventin çalıştırdığı kod parçacığını incelediğimizde ise, statüyü Modified olarak değiştiren yapıyı görebiliriz.
Person nesnesini, SavePerson methodu üzerinde herhangi bir değişiklik yapmadan bu haliyle kaydedebiliriz. Değişiklik yapılan Address nesnesi üzerindeki propertyler, veritabanında güncellenecektir.
Self Tracking Entity içerisindeki Collection Property’e ait bir nesnenin silinmesi
Person nesnesine ait Address nesnelerinden bir tanesinin silinmesi için, öncelikle ekran tarafında silme işlemini tetikleyecek “Seçili Adresi Sil” isimli bir buton yerleştirelim ve aşağıdaki kod parçasına benzer bir yapıyı çalıştırmasını sağlayalım.

Şekil 18 – Address nesnesini silme denemesi
Bu hali ile uygulamamızı çalıştırarak, bir tane Address nesnesini silerek SavePerson methodunu çağıralım ve veritabanından kontrol edelim. Address nesnesinin ekrandan silindiğini, ancak veritabanında hala bulunduğunu göreceğiz. Bunun sebebi, Address nesnesini collectiondan tamamen sildiğimizden, ApplyChanges methodunun böyle bir nesneden haberi olmaması ve dolayısıyla silineceğini de bilmemesidir. Bir nesnenin silineceğine dair bilgi ise, nesne üzerindeki MarkAsDeleted methodu ile o nesne üzerine aktarılır. Ekran tarafıdaki kodu aşağıdaki şekilde değiştirelim.

Şekil 19 – Address nesnesini silme denemesi
Bu hali ile tekrar çalıştıralım ve Address nesnesini silmek için butona tıklayalım. Ekranda ve daha sonrasında debug aşamasında görüleceği üzere, nesne Deleted olarak işaretlenmesine rağmen ekrandan silinmemiştir. Bunun sebebi, nesnenin hala collection üzerinden bulunuyor olmasıdır. Son kullanıcının kafasının karışmasını engellemek ve bu nesneyi ekrandan silmek için, grid üzerine aşağıdakine benzer bir filtre ekleyebiliriz.

Şekil 20 – Silinen Address nesneleri için grid filtresi
Bu filtreyi de ekledikten sonra uygulamamızı tekrar test edersek, silinen Address nesnelerinin hem ekrandan, hem de veritabanından başarıyla silindiğini göreceğiz. Böylelikle, collection içerisindeki bir nesneyi silmek için onu collectiondan remove etmek değil, onu MarkAsDeleted methodu ile Deleted olarak işaretlememiz gerektiğini öğrendik. Bu tip manuel statü değişiklikleri uygulamak amacı ile, nesne üzerinde aşağıdaki extension methodlar bulunmaktadır.

Şekil 21 – MarkAs methodları
Sonuç
Geliştirdiğimiz örnekle birlikte, Self Tracking Entity’lerin nasıl kullanıldığını, servis tabanlı kullanıma nasıl uygun hale getirildiğini, CRUD işlemlerin (kaydetme-değiştirme-silme) nasıl yapılabileceğini ve karşılaşılması muhtemel problemleri incelemiş olduk.
Notlar:
- Ekran geliştirme esnasında kullanılan grid ve combobox nesneleri DevExpress firmasının WPF componentleridir.
- Yazı Entity Framework’e ait Self Tracking Entity yapısını anlatmayı amaçladığından, WPF ve WCF’in kullanımına dair detaylar içermemektedir.
Kaynaklar: