https://buraksenyurt.com/Burak Selim Şenyurt - Entity Framework2023-02-26T02:02:10+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/Entity-Framework-Generic-Repository-ve-Unit-of-Work-UyarlamasiEntity Framework - Generic Repository ve Unit of Work Uyarlaması2014-12-14T03:00:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/thinking.jpg"><img style="background-image: none; float: right; padding-top: 0px; padding-left: 0px; margin: 4px 0px; display: inline; padding-right: 0px; border: 0px;" title="thinking" src="/pics/thinking_thumb.jpg" alt="thinking" width="240" height="232" align="right" border="0" /></a>Merhaba Arkadaşlar,</p>
<p>Yazılım dünyasında var olan mimari prensipler veya tasarım kalıpları tek başlarına belirli sorunları çözseler de, bazı kurumsal projelerde mutlak suretle bir arada düşünülmeleri gerekir. Söz gelimi <strong>Repository</strong> ve <strong>Unit of Work</strong> kalıpları, özellikle <strong>Domain Driven Design</strong> odaklı yapılarda bir arada değerlendirilmesi gerekenlerdendir.</p>
<p><strong>DDD </strong>denilince aklımıza daha çok veri odaklı uygulamalar gelir ve bu tip ürünlerde <strong>RDBMS<em>(Relational Database Management System) </em></strong>lerin yeri hatırı sayılır ölçüde fazladır<em>(Her ne kadar son yıllarda NoSQL cephesinde önemli gelişmeler ve kullanımda ciddi artışlar olsa da…) </em></p>
<p>Hal böyle olunca <strong>O/RM<em>(Object Relational Mapping)</em></strong> araçlarının kullanımı da önem kazanmaktadır. Yıllardır hayatımızda olan bu araçlar modellerin nesnel olarak inşasında da önemli bir yere sahiptirler. Lakin <strong>Object Oriented </strong>dünyasının kuralları içerisinde yaşarlar ve bu yüzden bazı kurumsal prensipleri uygulamaları gerekmektedir.</p>
<p>Benim gibi <strong>.Net</strong> üzerinde geliştirme yapanlar için O/RM araçları da az çok bellidir. <strong>Entity Framework</strong> bunlardan birisidir. Ancak <strong>Entity Framework</strong>’ ün uygulamalardaki kullanımında genellikle hatalar yapılır. <strong>Enterprise</strong> bir çözüm söz konusu olduğunda varsayılan olarak <strong>Data Access</strong> ve <strong>Business Logic</strong> katmanlarının olması izolasyon açısından önemlidir. İşte bu noktada <strong>DAL</strong> ve <strong>BLL</strong> arasındaki kullanımlarda <strong>EF</strong>’in çoğu zaman bir O/RM aracı olarak soyutlanamadığı görülür. Hal böyle olunca sistemin farklı bir kaynağı kullanarak yaşamaya devam etmesi de zorlaşır. <a href="http://martinfowler.com/eaaCatalog/repository.html" target="_blank">Repository</a> ve <a href="http://martinfowler.com/eaaCatalog/unitOfWork.html" target="_blank">Unit of Work</a> özellikle bu vakalara çözüm niteliğindeki iki değerli desendir.</p>
<blockquote>
<p>Hiç kimse bu deseneleri Martin Fowler kadar iyi açıklayamaz. Bu yüzden makalenin amacı ilgili desenelerin Entity Framework için örnek bir kullanımının anlatımından ibarettir.</p>
</blockquote>
<h1>İşin Gerçeği</h1>
<p>Gerçek hayatta <strong>Entity Framework</strong> veya başka bir <strong>O/RM</strong> aracının kullanıldığı hallerde aşağıdaki grafikteki iki durumdan birisi söz konusu olur<em>(Genellikle de en soldaki)</em>. Klasik olarak <strong>DbContext</strong> doğrudan iş katmanında değerlendirilir. Ancak <strong>Test Driven Development </strong>veya <strong>Domain Driven Design</strong> gibi yaklaşımların kullanıldığı geliştirme süreçlerinde, <strong>Repository</strong> ve <strong>Unit of Work</strong> desenelerinin icra edilmesi önemlidir. Nitekim bu sayede uygulamanın iş mantığının tutulduğu katman ile veri erişim katmanının izole edilmesi kolaylaşır. t anında farklı bir <strong>Repository</strong> ile çalışılabilmesi veya yenilerinin yazılarak sisteme dahil edilmesinin yolu açılır. Aynı kolaylık <strong>Unit of Work</strong> yapıları için de geçerlidir.</p>
<p><a href="https://buraksenyurt.com/pics/ruof_1.png"><img style="margin: 4px 0px; display: inline;" title="ruof_1" src="/pics/ruof_1_thumb.png" alt="ruof_1" width="600" height="368" /></a></p>
<p>İlk senaryoya göre iş mantığı, veri erişimi ve EF arasında kuvvetli bağlar oluşur. Bu sebepten, üründe kullanılan veri tabanını değiştirmek<em>(farklı bir Repository’ yi tercih etmek)</em> ve özellikle <strong>Unit Test </strong>gibi yapılarda <strong>Mock</strong> nesneleri değerledirmek zorlaşır. Bir <strong>Unit Test </strong>metodu içerisindeki işlemler bütününde her zaman <strong>CRUD<em>(CreateReadUpdateDelete)</em></strong> operasyonları icra edilmek istenmeyebilir. Nitekim iş bütününün <strong>Repository</strong> odaklı olmayan kısımlarının test edilmesi de söz konusudur.</p>
<p><strong>Unit Test’</strong> lerin çalıştığı geliştirme ortamının hiç bir şekilde bir veri kaynağına gidemediği hallerde geri kalan kısmın test edilme ihtiyacı bu tip bir gereksinimdir. Ayrıca aynı veri kaynağı ile çalışılacak diye bir kural yoktur. <strong>Domain</strong> içerisindeki <strong>Entity</strong> modelleri sabit kalabilir ve iş kuralları çok az değişiklik gösterebilir. Ama verilerin yazıldığı ortamlar duruma göre farklılık gösterebilir, açılıp kapatılmak istenebilir. Bu sebeple soyutlama(abstraction) yapmak ve uygun sözleşme tanımlamalarını<em>(Interface bildirimleri diyebiliriz) </em>işin içerisine katmak önemlidir.</p>
<p>Gelin konuyu basit ve pek de işe yaramayacak örnek bir senaryo üzerinden ele alalım. Amacımız içinde iki <strong>Entity</strong> barındıran bir <strong>DbContext</strong> türevini, <strong>Repository</strong> ve <strong>Unit of Work</strong> desenleri çerçevesinde nasıl ele alabileceğimizi incelemektir.</p>
<h1>Code First ile Entity Modelin İnşası</h1>
<p>Örnek uygulama her zaman ki gibi gösterişsiz bir <strong>Console</strong> projesidir. Amaç ilgili desenlerin sade bir uyarlamasını görebilmektir. Ama öncesinde <strong>NuGet</strong> üzerinden güncel <strong>Entity</strong> <strong>Framework’</strong> ün son sürümü projeye indirilerek işe başlanabilir.</p>
<p><a href="https://buraksenyurt.com/pics/rpuow_1.png"><img style="margin: 4px 0px; display: inline;" title="rpuow_1" src="/pics/rpuow_1_thumb.png" alt="rpuow_1" width="640" height="360" /></a></p>
<p>Ardından kobay olarak aşağıdaki <strong>Entity</strong> sınıfları ve <strong>DbContext</strong> türevini yazabiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/ruof_2.png"><img style="margin: 4px 0px; display: inline;" title="ruof_2" src="/pics/ruof_2_thumb.png" alt="ruof_2" width="306" height="482" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.Data.Entity;
namespace RPandUOW.EntityModel
{
public class ShopContext
: DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string Title { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public int ProductID { get; set; }
public string Title { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
}
}</pre>
<p>Tipik olarak <strong>one-to-many</strong> ilişki içerisinde sayabileceğimiz iki <strong>POCO</strong> tipi bulunmaktadır. Bir kategori ve bu kategoriye bağlı olan ürünler. Gelelim Repository deseninin uygulanış biçimine.</p>
<h1>Repository Yapısının İnşası</h1>
<p>Öyle bir yapı kurgulamalıyız ki, hem bir <strong>Repository</strong> için gerekli minimum fonksiyonelliklerin bir sözleşmesi hem de <strong>Context</strong> içerisinde yer alan her <strong>T</strong> tipi için çalışabilecek generic bir sınıf olsun. Ve pek tabi varsayılan kuralları istediği gibi işleyecek yeni <strong>Repository’</strong> leri yazmanın da yolu açılabilsin. İlk olarak aşağıdaki sınıf diagramında görülen tiplerin tasarlanmasıyla işe başlanabilir.</p>
<p><a href="https://buraksenyurt.com/pics/rpuow_2.png"><img style="margin: 4px 0px; display: inline;" title="rpuow_2" src="/pics/rpuow_2_thumb.png" alt="rpuow_2" width="437" height="492" /></a></p>
<p>ve kodlar;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using RPandUOW.EntityModel;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace RPandUOW.Repositories
{
public interface IGenericRepository<T>
where T:class
{
T FindById(object EntityId);
IEnumerable<T> Select(Expression<Func<T, bool>> Filter = null);
void Insert(T Entity);
void Update(T Entity);
void Delete(object EntityId);
void Delete(T Entity);
}
public class ShopRepository<T>
:IGenericRepository<T>
where T:class
{
private ShopContext _context;
private DbSet<T> _dbSet;
public ShopRepository(ShopContext Context)
{
_context = Context;
_dbSet = _context.Set<T>();
}
public virtual T FindById(object EntityId)
{
return _dbSet.Find(EntityId);
}
public virtual IEnumerable<T> Select(Expression<Func<T, bool>> Filter = null)
{
if (Filter != null)
{
return _dbSet.Where(Filter);
}
return _dbSet;
}
public virtual void Insert(T entity)
{
_dbSet.Add(entity);
}
public virtual void Update(T entityToUpdate)
{
_dbSet.Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void Delete(object EntityId)
{
T entityToDelete = _dbSet.Find(EntityId);
Delete(entityToDelete);
}
public virtual void Delete(T Entity)
{
if (_context.Entry(Entity).State == EntityState.Detached) //Concurrency için
{
_dbSet.Attach(Entity);
}
_dbSet.Remove(Entity);
}
}
}</pre>
<p>Burada neler yaptık, ortalığı nasıl karıştırdık incelemeye çalışalım. <strong>IRepository<T></strong> arayüzü içerisinde bir <strong>Repository</strong> için söz konusu olabilecek temel fonksiyonların tanımlandığını görmekteyiz. <strong>CRUD(CreateReadUpdateDelete)</strong> operasyonları olarak adlandırabileceğimiz metodlar ile bir <strong>Repository’</strong> nin minimumda sahip olması gereken sözleşmeyi de tanımlamış oluyoruz.</p>
<p><strong>ShopRepository<T></strong> sınıfı dikkat edileceği üzere <strong>IRepository<T></strong> arayüzünü uygulamakta ve kendi içerisinde <strong>DbContext</strong> sınıfından türetilmiş bir <strong>ShopContext</strong> örneğini kullanmaktadır. Yani <strong>ShopRepository</strong> generic sınıfı, <strong>ShopContext</strong> içinde tanımlı herhangi bir <strong>T </strong>tipini kullanarak <strong>CRUD</strong> operasyonlarını gerçekleştirebilir. Bunun bir diğer anlamıda, farklı kaynakları kullanan veya Mock nesne olabilen <strong>Repository</strong> tiplerinin istenildiği zaman sisteme dahil edilebilmesidir. Tek yapılması gereken ilgili <strong>IRepository<T></strong> sözleşmesinin yeni Repository için uygulanmasından başka bir şey değildir.</p>
<p><strong>Repository</strong>’ nin kullandığı <strong>Context</strong> nesnesinin oluşturulması aslında yapıcı metod içerisinde icra edilmektedir. Burada da generic bir kullanım yolu düşünülebilir. Dikkat çekici noktalardan bir tanese bir <strong>Context</strong> için söz konusu olan <strong>Save</strong> işleminin bu tiplerde her angi bir biçimde ele alınmamış olmasıdır. Aslında bu, <strong>Unit of Work</strong> yapısının inşasında ele alınması gereken bir fonksiyonelliktir. Öyleyse Unit of Work yapısını tesis etmeye başlayabiliriz.</p>
<h1>Unit of Work Yapısının İnşası</h1>
<p><strong>Entity Framework</strong> açısından bir birimlik işi; içerisinde konuya dahil olması gereken <strong>Repository</strong> örneklerinin oluşturulması ve <strong>Save</strong> işleminin icra edilmesi olarak düşünebiliriz<em>(Hatta bu yapı içerisine <strong>Transaction</strong> açılıp kapatılması da dahil edilebilir)</em> Pek tabi Unit of Work yapısınında bir sözleşme üzerinden değerlendirilmesi, farklı Unit of Work’ lerin de değerlendirilebilmesi açısından önemlidir. Bu düşünceler ışığında aşağıdaki yapıyı kurgulayabiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/rpuow_3.png"><img style="margin: 4px 0px; display: inline;" title="rpuow_3" src="/pics/rpuow_3_thumb.png" alt="rpuow_3" width="358" height="432" /></a></p>
<p>ve kodlar;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using RPandUOW.EntityModel;
using RPandUOW.Repositories;
using System;
using System.Transactions;
namespace RPandUOW.UnitOfWorks
{
public interface IUnitOfWork
:IDisposable
{
void Save();
// Başka operasyonlar da tanımlanabilir.
// void OpenTransaction();
// void CloseTransaction();
// gibi
}
public class ShopUnitOfWork
:IUnitOfWork
{
private ShopContext _context = new ShopContext();
private ShopRepository<Category> _categoryRepository;
private ShopRepository<Product> _productRepository;
private bool _disposed = false;
public ShopRepository<Category> CategoryRepository
{
get
{
if (_categoryRepository == null)
_categoryRepository = new ShopRepository<Category>(_context);
return _categoryRepository;
}
}
public ShopRepository<Product> ProductRepository
{
get
{
if (_productRepository == null)
_productRepository = new ShopRepository<Product>(_context);
return _productRepository;
}
}
public void Save()
{
using (TransactionScope tScope = new TransactionScope())
{
_context.SaveChanges();
tScope.Complete();
}
}
protected virtual void Dispose(bool disposing)
{
if (!this._disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this._disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}</pre>
<p>Pek tabi soyutlama amacıyla <strong>IUnitOfWork</strong> isimli bir arayüz tanımlanarak işe başlanmıştır. Arayüz şu an için Save metodunun uygulanması gerektiğini belirtir. Tabi bir de <strong>IDisposable</strong> arayüzü nedeniyle <span style="font-weight: bold;">Dispose</span> metodunun ezilmesi zorunlıdır. Bir birimlik iş için ihtiyaca göre başka genel fonksiyonellikler de sözleşme içerisine dahil edilebilir. Örneğin bir <strong>Transaction</strong> açılması ve kapatılması için gerekli metodlar sözleşme ile zorunlu tutulabilir. Tabi bunu çok da spesifik düşünmemek gerekir. Nitekim kimi <strong>Repository</strong>’ lerin, kullandığı veri kaynakları bir <strong>Transaction</strong> ile çalışmak zorunda olmayabilir. Hatta ortada bir veri kaynağı da bulunmayabilir<em>(Burada Mock nesnelere atıfta bulunmaktayım)</em></p>
<p><strong>ShopContext</strong> için kullanılacak <strong>Unif of Work</strong> kurgusunda ise, işe dahil olacak <strong>Repository’</strong> ler birer <strong>Property</strong> olarak tanımlanmış ve sadece okunabilir şekilde son kullanıcıya sunulmuşlardır. Üretim işlemleri sırasında yapılan <strong>null</strong> kontrolü, <strong>Unit of Work</strong> nesnesinin yaşamı boyunca, tüm <strong>Repository’</strong> lerin aynı <strong>Context</strong> tipini<em>(ki örnekte _context isimli ShopContext örneğidir) </em>kullanması açısında önemlidir. <em>(Bu durumu daha iyi anlamak için debug modda çalışmanızı öneririm)</em></p>
<p>Bir başka deyişle örneğin <strong>Save</strong> işlemi sırasında tüm <strong>Repository</strong> nesnelerinin aynı <strong>DbContext</strong> örneği üzerinden işlemlerini gerçekleştirmesi ve tek bir <strong>Transaction </strong>bütünlüğü içerisinde çalışması sağlanmış olmaktadır. Zaten <strong>Unit of Work</strong> desenin temel amaçlarından birisi de bu işlem bütünlüğünü kurgulamaktr.</p>
<h1>Basit Bir Kullanım</h1>
<p>Yazılan <strong>Unit of Work</strong> uyarlamasının uygulanış biçimi oldukça kolaydır. Normal şartlarda bir <strong>BLL</strong> fonksiyonelliği içerisinde de değerlendirilebilir. Konunun basitçe ele alınması açısından <strong>Main </strong>metodu aşağıdaki kodları içerecek şekilde geliştirilmiştir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using RPandUOW.EntityModel;
using RPandUOW.UnitOfWorks;
using System;
using System.Collections.Generic;
namespace RPandUOW
{
class Program
{
static void Main(string[] args)
{
using (ShopUnitOfWork worker = new ShopUnitOfWork())
{
Category computerBook = new Category { Title = "Computer Books" };
worker.CategoryRepository.Insert(computerBook);
computerBook.Products = new List<Product> {
new Product { Title = "Advanced NoSQL", Quantity = 1, UnitPrice = 34.59M },
new Product { Title = "NHibernate in Action", Quantity = 5, UnitPrice = 29.99M },
new Product { Title = "Unleashed Delphi 2.0", Quantity = 3, UnitPrice = 9.99M }
};
Category cookBook = new Category { Title = "Cook Books" };
worker.CategoryRepository.Insert(cookBook);
cookBook.Products = new List<Product> {
new Product()
{
Title = "Italian Kitchen", Quantity = 20, UnitPrice = 12 }
};
worker.CategoryRepository.Insert(cookBook);
worker.Save();
var books = worker.ProductRepository.Select(p => p.CategoryID == computerBook.CategoryID);
foreach (var book in books)
{
Console.WriteLine("{0} {1} {2}", book.Title, book.UnitPrice, book.Quantity);
}
}
}
}
}</pre>
<p><a href="https://buraksenyurt.com/pics/rpuow_4.png"><img style="margin: 4px 0px; display: inline;" title="rpuow_4" src="/pics/rpuow_4_thumb.png" alt="rpuow_4" width="324" height="131" /></a></p>
<p><strong>IDisposable</strong> arayüzü implementasyonu nedeniyle <strong>ShopUnitOfWork</strong> sınıfı <strong>using</strong> bloğun içerisinde kullanılabilir. Zaten <strong>dipose</strong> işlemi sınıfın içerisinde <strong>override</strong> edilmiştir. Blok içerisinde bir dizi örnek işlem icra edilmektedir. Buna göre bir kaç kategorinin ve bu kategorilere bağlı ürünlerin eklenmesi işlemi ele alınmaktadır. <strong>Save</strong> işlemi, <strong>Unit of Work</strong> uyarlamasının bir fonksiyonu olduğundan, dahil edilen tüm <strong>Repository</strong> örnekleri için ortak bir kullanım noktasıdır. Öyle ki, örnekte asıl <strong>Context</strong> nesnesi üzerinden yapılan kaydetme işleminin bir <strong>TransactionScope</strong> içerisinde gerçekleştirilmesi sağlanmaktadır.</p>
<p>Görüldüğü üzere <strong>Repository</strong> ve<strong> Unit of Work</strong> desenelerini <strong>Entity Framework</strong> tarafında uygulamak oldukça kolaydır. Kaynaklarda bu desenlerin daha etkili uygulanış biçimlerini de görebilirsiniz. Örneğin <a href="http://genericunitofworkandrepositories.codeplex.com/" target="_blank">Codeplex’ in şu adresindeki</a> uygulanış tarzı beni etkileyenler arasındadır. Hatta <strong>Unit of Work</strong> uyarlamasının daha <strong>generic</strong> ve <strong>Context’</strong> lere gevşek bağlı<em>(Loosely Coupled)</em> olan bir versiyonu da yazılabilir<em>(İşin içine Dependency Injection da katılıp olay daha bir renkli hale getirilebilir)</em> Bunlara biraz kafa yormakta fayda vardır. </p>
<p>Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2014-12-14T03:00:00+00:00entity frameworkgeneric repositorygenericsunit of workdesign patternssoftware design principlesbsenyurtDDD denilince aklımıza daha çok veri odaklı uygulamalar gelir ve bu tip ürünlerde RDBMS(Relational Database Management System) lerin yeri hatırı sayılır ölçüde fazladır(Her ne kadar son yıllarda NoSQL cephesinde önemli gelişmeler ve kullanımda ciddi artışlar olsa da…) Hal böyle olunca O/RM(Object Relational Mapping) araçlarının kullanımı da önem kazanmaktadır. Yıllardır hayatımızda olan bu araçlar modellerin nesnel olarak inşasında da önemli bir yere sahiptirler. Lakin Object Oriented dünyasının kuralları içerisinde yaşarlar ve bu yüzden bazı kurumsal prensipleri uygulamaları gerekmektedir.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=c323fd11-1192-4395-8cd4-d3fa04a4ee6d23https://buraksenyurt.com/trackback.axd?id=c323fd11-1192-4395-8cd4-d3fa04a4ee6dhttps://buraksenyurt.com/post/Entity-Framework-Generic-Repository-ve-Unit-of-Work-Uyarlamasi#commenthttps://buraksenyurt.com/syndication.axd?post=c323fd11-1192-4395-8cd4-d3fa04a4ee6dhttps://buraksenyurt.com/post/Entity-Frameworke28093Iki-Entity-Bir-Table-ile-Lazy-ve-Eager-LoadingEntity Framework–İki Entity Bir Table ile Lazy ve Eager Loading2014-10-12T02:00:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/eftsplit_0.png"><img style="float: right; margin: 4px 0px; display: inline;" title="eftsplit_0" src="/pics/eftsplit_0_thumb.png" alt="eftsplit_0" width="318" height="226" align="right" /></a>Merhaba Arkadaşlar,</p>
<p>Yandaki görüntü <strong>1988 </strong>yılında Mevlüt Dinç<em>(nam-ı diğer Mev Dinc) </em>tarafından kurulan <a href="http://en.wikipedia.org/wiki/Vivid_Image" target="_blank"><strong>Vivid Image</strong></a> firmasının geliştirdiği oyunlardan birisine ait. <strong><a href="http://en.wikipedia.org/wiki/First_Samurai_(video_game)" target="_blank">The First Samurai</a></strong>. <strong>Mev Dinc</strong> ülkemizin yetiştirdiği en önemli değerlerden birisidir. Kendisi ile NedirTv topluluğunda yapılmış güzel bir röportaj da bulunmaktadır. Pek çoğumuz onu, <strong><a href="http://www.sobee.com.tr" target="_blank">SOBEE</a> </strong>firması ile de tanımıştır. Ben ise uzun zaman önce <strong>Microsoft</strong>’ un <strong>Darphane</strong>’ deki binasında katıldığım bir söyleşiden…</p>
<p>MVP olduğum o dönemlerde Microsoft Türkiye düzenlediği bir etkinlik ile onu karşımıza çıkartmıştı. Kendisini büyük bir keyifle dinlemiştik. Nasıl bu günlere geldiğinden, geliştirdiği oyunlardan, kurduğu <strong>SOBEE </strong>firması' nın projelerinden bahsetmişti. Hatta akılda kalan önemli ifadelerden birisi de, yeni geliştirmekte oldukları oyunlarda <strong>C++ </strong>yerine <strong>C# </strong>programlama dilini tercih etmeleriydi. <em>(Sene 2007 olabilir) </em>Ancak benim daha çok aklımda kalan tam olarak hatırlayamasam da aşağıda yazan ifadeleriydi.</p>
<blockquote>
<p>Bir savaş oyununda binanın tamamını düşünmeye gerek yoktur. O an için sadece aktörlerin bulunduğu odayı düşünmek yeterlidir.</p>
</blockquote>
<p>Şimdi nereden geldik bu sözlere. Geçtiğimiz günlerde <strong>Entity Framework</strong> üzerine bir takım araştırmalar yaparken <strong>Lazy </strong>ve <strong>Eager Loading </strong>işlemlerinin hangi noktalarda kullanılabileceğine dair bazı yararlı bilgiler edindim. Bununla birlikte bir senaryo gerçekten dikkatimi çekti.</p>
<h1>Temel Gereksinim</h1>
<p>Özellikle içerisinde <strong>CLOB </strong>veya <strong>BLOB</strong> benzeri alanlar barındıran tabloların <strong>Entity Framework</strong> tarafındaki kullanımlarında network yükünü hafifletmek adına bir tablonun iki <strong>Entity </strong>ile ifade edilebilmesi düşünülebilir. Böyle bir durumda <strong>Lazy Loading</strong>’ i tablo içerisindeki alanlar bazında uygulama şansına sahip oluruz. Bu, özellikle <strong>LINQ<em>(Language INtegrated Query)</em> </strong>sorgularını işlettiğimiz yerlerde performans açısından faydalı bir iyileştirmedir. Kısacası bir tablonun kendi alanları içerisinde ilişki kurup bunu <strong>Entity </strong>seviyesinde ifade etmemiz gerekmektedir. Gelin basit bir örnek üzerinden ilerleyerek konuyu incelemeye çalışalım.</p>
<blockquote>
<p>Console Application formundaki örnekte yazının yazıldığı tarih itibariyle Entity Framework 6.1.1 sürümü kullanılmıştır. Entity Framework, NuGet paket yönetim aracı ile projeye dahil edilebilir.</p>
</blockquote>
<h1>Model</h1>
<p><strong>Code First</strong> yaklaşımını baz alarak geliştireceğimiz örnekte ilk olarak aşağıdaki sınıf çizelgesinde<em>(Class Diagram)</em> yer alan tipleri inşa ederek işe başlayabiliriz. Temel olarak <strong>PDF</strong>,<strong>WMV </strong>gibi yüksek boyutlarda ve <strong>binary</strong> formatta ifade edilebilen veri içeriklerini taşıyacak bir <strong>SQL </strong>tablosunun iki farklı <strong>Entity </strong>ile ifade edildiğini söyleyebiliriz. <em>(Ki bu sebepten <strong>DocumentContent </strong>isimli sınıf içerisinde yer alan <strong>Content </strong>ve <strong>FrontCover </strong>özellikleri <strong>byte[]</strong> tipinden tanımlanmışlardır)</em></p>
<p><a><img style="margin: 4px 0px; display: inline;" title="eftsplit_2" src="/pics/eftsplit_2_thumb.png" alt="eftsplit_2" width="517" height="389" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace HowTo_EFTable
{
public class CompanyBookContext
:DbContext
{
public DbSet<Document> Document { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Document>().ToTable("Documents");
modelBuilder.Entity<DocumentContent>().ToTable("Documents");
modelBuilder.Entity<Document>()
.HasRequired(c => c.Content)
.WithRequiredPrincipal();
}
}
public class Document
{
[Key]
public int DocumentID { get; set; }
public string Title { get; set; }
public int PageCount { get; set; }
public string Language { get; set; }
public string Genre { get; internal set; }
public string Publisher { get; set; }
public virtual DocumentContent Content { get; set; }
public string ISBN { get; internal set; }
}
public class DocumentContent
{
[Key]
public int DocumentID { get; set; }
public byte[] Content { get; set; }
public byte[] FrontCover { get;set; }
}
}</pre>
<p>Pek tabi dikkat edilmesi gereken önemli noktalar bulunmaktadır. Söz gelimi <strong>Document </strong>ve aslında büyük boyutlu içerikleri barındıran <strong>DocumentContent </strong>sınıfları arasında bir ilişki vardır. Öyleki her ikisi de aslında aynı tabloyu işaret etmelidir. Bunun için her iki sınıfın <strong>DocumentID </strong>isimli özellikleri <strong>Key </strong>nitelikleri(attribute) ile işaretlenmiştir.</p>
<blockquote>
<p>Entity tiplerinde Key niteliği kullanılmazsa kuvvetle muhtemel aşağıdaki gibi bir çalışma zamanı hatası alınacaktır.<a href="https://buraksenyurt.com/pics/eftsplit_1.png"><img style="margin: 4px 0px; display: inline;" title="eftsplit_1" src="/pics/eftsplit_1_thumb.png" alt="eftsplit_1" width="584" height="140" /></a></p>
</blockquote>
<p><strong>Document </strong>tipi içerisinde yer alan <strong>Content </strong>isimli özellik aslında <strong>DocumentContent </strong>tipindedir ve bir <strong>Navigation Property </strong>şeklinde düşünülebilinir.<em>(Sanki iki farklı tablo arasında one-to-one Relation kuruyoruz gibi düşünebiliriz)</em></p>
<p>Çok doğal olarak <strong>Code First </strong>yaklaşımının kullanıldığı bu örnekte modelin inşası sırasında da bazı özel işlemler yapılması gerekmektedir. Nitekim veri tabanı üzerinde tek bir tablo olması planlanmaktadır ve model’in içinde yer alan iki <strong>Entity</strong>’ nin aslında aynı tabloyu işaret edeceği veri tabanı nesnelerinin üretimi sırasında söylenebilmelidir.</p>
<p>Bu işlem <strong>ezilen<em>(Override)</em></strong> <strong>OnModelCreating </strong>metodu içerisinde yapılmaktadır. Dikkat edileceği üzere <strong>Document </strong>ve <strong>DocumentContent </strong>isimli <strong>Entity </strong>tiplerinin aynı tablo’ yu<em>(ki örnekte Documents) </em>işaret ettikleri belirtilmektedir.</p>
<h1>Bu arada Config</h1>
<p>Uygulamada <strong>SQL Server </strong>kullanılmaktadır. Bu nedenle <strong>config </strong>dosyası içerisindeki <strong>connectionStrings </strong>elementi içeriği önemlidir. Hatırlanacağı üzere <strong>DbContext </strong>türevli sınıf adı ile aynı isimde bir <strong>connectionString </strong>elementinin bulunması gerekmektedir. Örnekte aşağıdaki bağlantı bilgisi kullanılmıştır.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="CompanyBookContext" connectionString="data source=.;database=Azon;integrated security=SSPI;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration></pre>
<p> </p>
<h1>Ana Uygulama Kodları</h1>
<p>Örnekte temel olarak <strong>Lazy </strong>ve <strong>Eager Loading </strong>operasyonlarının özellikle <strong>SQL Script'</strong> ler bazında nasıl olduğu üzerinde durulmaktadır. Bu nedenle aşağıdaki anlamsız kod içeriği ele alınabilir. Bizim için önemli olan arka planda yürütülen <strong>SQL </strong>betikleridir.</p>
<p>Program.cs</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.IO;
using System.Linq;
namespace HowTo_EFTable
{
class Program
{
static void Main(string[] args)
{
byte[] samplePDF = File.ReadAllBytes(@"c:\DomainDrivenDesignQuicklyOnline.pdf");
byte[] sampleCover= File.ReadAllBytes(@"c:\SampleCover.png");
using (CompanyBookContext context=new CompanyBookContext())
{
context.Database.Log = Console.Write; // SQL Script' lerini izlemek için Log çıktısını Console olarak set ettik.
Document someBook = new Document
{
Language = "TR",
ISBN="1234-3456-BOOK-1202",
Title = "Domain Driven Design Quickly - Online Edition",
Genre="Computer Books",
PageCount = 348,
Publisher="Your Best Publisher",
Content = new DocumentContent
{
FrontCover =sampleCover,
Content =samplePDF
}
};
context.Document.Add(someBook);
context.SaveChanges();
#region Lazy Loading
var dcmnt = (from d in context.Document
where d.ISBN == "1234-3456-BOOK-1202"
select d).FirstOrDefault();
if (dcmnt != null)
{
byte[] bookContent = dcmnt.Content.Content;
Console.WriteLine(bookContent.Length.ToString()); // bookContent' i her hangibir şekilde kullanmassak ikinci Select işlemi gerçekleşmez.
}
#endregion
#region Eager Loading
dcmnt = (from d in context.Document.Include("Content")
where d.ISBN == "1234-3456-BOOK-1202"
select d).FirstOrDefault();
if (dcmnt != null)
{
byte[] bookContent = dcmnt.Content.Content;
}
#endregion
}
}
}
}</pre>
<p>Örnekte <strong>Document </strong>tipinden bir nesne örneği üretilmektedir. Dikkat edilmesi gereken nokta <strong>Content </strong>özelliğine de <strong>DocumentContent </strong>tipinden bir örneğin atanmış olmasıdır. <strong>Document </strong>nesne örneğinin <strong>DbSet</strong>’ e eklenmesi işlemi veri tabanı tarafından <strong>Documents </strong>isimli tabloya bir <strong>insert </strong>işlemi olarak algılanmaktadır.</p>
<p>Kodun sonraki kısmında ise oluşturulan <strong>Document </strong>içeriğinin tablodan iki farklı şekilde çekilme işlemi söz konusudur. İlkinde <strong>Lazy Loading </strong>tekniğinin<strong> </strong>uygulaması söz konusudur. <strong>Document </strong>içeriği çekilirken <strong>DocumentContent </strong>tipinin taşıyacağı alanlar ilk etapta alınmazlar. Ta ki <strong>Content </strong>özelliği kodda kullanılana dek<em>(byte[] array' in boyutunun Console penceresine yazıldığı yer)</em></p>
<p><strong>Eager Loading </strong>tekniğinin uygulandığı<strong> </strong>durumda ise <strong>DocumentContent </strong>tipinin işaret ettiği <strong>Content </strong>ve <strong>FrontCover </strong>alanlarının, <strong>LINQ </strong>ifadesindeki <strong>Include </strong>terimi nedeniyle <strong>Select </strong>işlemi sırasında çekilmesi söz konusudur. Yani tüm tablo içeriği, <strong>Lazy Loading</strong>’ in aksine <strong>Select </strong>ifadesi ile birlikte gelmektedir.</p>
<h1>Çalışma Zamanı Analizi</h1>
<p>Aslında durumu daha iyi analiz etmek adına çalışma zamanı çıktılarına bakabiliriz. <strong>Log </strong>içeriğini <strong>Console </strong>penceresine yansıttığımızdan, arka planda çalıştırılan <strong>SQL Script</strong>' lerini kolayca görebiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/eftsplit_5.png"><img style="display: inline;" title="eftsplit_5" src="/pics/eftsplit_5_thumb.png" alt="eftsplit_5" width="493" height="662" /></a></p>
<p>Şimdi ilk <strong>LINQ </strong>ifadesini ele alalım<em>(Lazy Loading region’ lı kısım)</em></p>
<p>Arka planda ilk olarak <strong>Content </strong>ve <strong>FrontCover </strong>alanlarını içermeyen bir <strong>Select </strong>cümleciği çalıştırıldığı görülmektedir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT TOP(1)
[Extent1].[DocumentID] as [DocumentID],
[Extent1].[Title] as [Title],
[Extent1].[PageCount] as [PageCount],
[Extent1].[Language] as [Language],
[Extent1].[Genre] as [Genre],
[Extent1].[Publisher] as [Publisher],
[Extent1].[ISBN] as [ISBN]
FROM [dbo].[Documents] as [Extent1]
WHERE N'1234-3456-BOOK-1202'=[Extent1].[ISBN]</pre>
<p>Bu son derece doğaldır nitekim gelmeyen alanlar kodun o anki satırına kadar talep edilmemiştir. Ancak elde edilen <strong>Document </strong>nesne örneğinin <strong>Content </strong>özelliği üzerinden hareketle uzunluk bilgisi ekrana yazılmak istendiğinde, SQL tarafında ikinci bir <strong>Select </strong>ifadesinin çalıştırıldığı görülmektedir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT TOP(1)
[Extent1].[DocumentID] as [DocumentID],
[Extent1].[Content] as [Content],
[Extent1].[FrontCover] as [FrontCover]
FROM [dbo].[Documents] as [Extent1]
WHERE [Extent1].[DocumentID]=@EntityKeyValue1</pre>
<p>Aynı <strong>Where </strong>koşulu için çalıştırılan bu ifade de <strong>Documents </strong>tablosundan sadece <strong>FrontCover </strong>ve <strong>Content </strong>alanlarının çekildiğine dikkat edilmelidir. İşte bu, <strong>“ihtiyaç duyduğum yerde verileri yükle”</strong> felsefesi olarak yorumlanabilir. Kısaca <strong>Lazy Loading</strong>…</p>
<p>Gelelim ikinci ifadeye; Bu kez <strong>LINQ </strong>sorgusunda <strong>Include </strong>metodunun çağırıldığı ve parametre olarak <strong>Content </strong>isimli <strong>Navigation Property </strong>değerinin verildiği görülmektedir. Buna göre <strong>Documents </strong>tablosundaki tüm alanlar<em>(Document ve DocumentContent Entity tiplerine ait özelliklerin işaret ettikleri)</em> <strong>Select </strong>cümleciğine dahil edilmiştir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT TOP(1)
[Extent1].[DocumentID] as [DocumentID],
[Extent1].[Title] as [Title],
[Extent1].[PageCount] as [PageCount],
[Extent1].[Language] as [Language],
[Extent1].[Genre] as [Genre],
[Extent1].[Publisher] as [Publisher],
[Extent1].[ISBN] as [ISBN],
[Extent1].[Content] as [Content],
[Extent1].[FrontCover] as [FrontCover]
FROM [dbo].[Documents] as [Extent1]
WHERE N'1234-3456-BOOK-1202'=[Extent1].[ISBN]</pre>
<p>İşte bu da,<strong> “o an kullanmayacak olsam da tüm içeriği şu anda bana ver.”</strong> felsefesidir. Yani <strong>Eager Loading</strong>…</p>
<p>Tabi program çalıştırıldığında modelin inşasının sonuçlarına da bakılmalıdır. <strong>SQL Management Studio </strong>ile ilgili veritabanına gidilirse <strong>Documents </strong>isimli tek bir tablonun aşağıdaki şekildeki gibi oluşturulduğu görülebilir.</p>
<p><a><img style="margin: 4px 0px; display: inline;" title="eftsplit_3" src="/pics/eftsplit_3_thumb.png" alt="eftsplit_3" width="287" height="337" /></a></p>
<p>Ayrıca kod tarafında gerçekleştirilen <strong>Insert </strong>işlemi sonrasında, iki farklı <strong>Entity </strong>örneğindeki özellik değerlerinin tek bir satır içerisine yerleştirildiği fark edilebilir.</p>
<p><a href="https://buraksenyurt.com/pics/eftsplit_4.png"><img style="margin: 4px 0px; display: inline;" title="eftsplit_4" src="/pics/eftsplit_4_thumb.png" alt="eftsplit_4" width="640" height="154" /></a></p>
<h1>Sonuç</h1>
<p>Aslında yazımızın başında da belirttiğimiz ve <strong>Mev Dinc</strong>’ in ifade ettiği üzere, bazen olay anında ve yerinde tüm detayın olmasına gerek yoktur. O anda sadece bulunması gereken verilere ihtiyaç vardır. Bu felsefe görüldüğü üzere sadece oyun programlama tekniklerinde değil farklı konularda da karşımıza çıkmaktadır.Tabi bu felsefe kurgunun çok iyi yapılmasını gerektirir. Lazy ve Eager Loading teknikleri, diğer ORM araçlarında olduğu gibi Entity Framework’ ün de olmassa olmazlarıdır. Makalemizde ele aldığımız konu ince bir performans ayarını işaret etmektedir. Bir tabloyu kod tarafında parçalı şekilde ifade edebilmek, gerekli parçalarının <strong>Lazy Loading</strong> ile yüklenmesinin yolunu açmaktadır. Konu hakkında daha detaylı bilgiye <a href="http://visualstudiomagazine.com/articles/2014/09/01/splitting-tables.aspx" target="_blank"><strong>Peter Vogel</strong>' in <strong>MSDN Magazine</strong>' de yayınlanan makalesinden</a> ulaşabilirsiniz. </p>
<p>Böylece geldik bir makalemizin daha sonuna. Bir bakşa makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>2014-10-12T02:00:00+00:00entity frameworklazy loadingeager loadingcode first developmentbsenyurtÖzellikle içerisinde CLOB veya BLOB benzeri alanlar barındıran tabloların Entity Framework tarafındaki kullanımlarında network yükünü hafifletmek adına bir tablonun iki Entity ile ifade edilebilmesi düşünülebilir. Böyle bir durumda Lazy Loading’ i tablo içerisindeki alanlar bazında uygulama şansına sahip oluruz. Bu, özellikle LINQ(Language INtegrated Query) sorgularını işlettiğimiz yerlerde performans açısından faydalı bir iyileştirmedir. Kısacası bir tablonun kendi alanları içerisinde ilişki kurup bunu Entity seviyesinde ifade etmemiz gerekmektedir. Gelin basit bir örnek üzerinden ilerleyerek konuyu incelemeye çalışalım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=14fc3656-a943-464c-81c6-0ff7b211fb322https://buraksenyurt.com/trackback.axd?id=14fc3656-a943-464c-81c6-0ff7b211fb32https://buraksenyurt.com/post/Entity-Frameworke28093Iki-Entity-Bir-Table-ile-Lazy-ve-Eager-Loading#commenthttps://buraksenyurt.com/syndication.axd?post=14fc3656-a943-464c-81c6-0ff7b211fb32https://buraksenyurt.com/post/Entity-Framework-6-e28093Yeni-EsintilerEntity Framework 6 –Yeni Esintiler2014-04-08T11:09:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/ef61_6.png"><img style="background-image: none; float: right; padding-top: 0px; padding-left: 0px; margin: 4px 0px; display: inline; padding-right: 0px; border: 0px;" title="ef61_6" src="/pics/ef61_6_thumb.png" alt="ef61_6" width="280" height="168" align="right" border="0" /></a>Merhaba Arkadaşlar,</p>
<p>Tam da bu gün İstanbul’ da hafif rüzgarlı, güneşli bir bahar havası var. Binaların kapalı mekanlarında çalışan bizler için iki dakikalığına da olsa dışarıya çıkmanın, rüzgarın hafif esintisini ve güneşin ılık sıcaklığını hissetmenin değeri paha biçilemez.</p>
<p>Bahara olan özlemimizin tavan yaptığı bu günlerde, başka diyarlarda da değişik esintiler söz konusu elbette. <a href="https://buraksenyurt.com/post/C-60e28093Yeni-Esintiler" target="_blank">Örneğin C# 6.0 da</a>, örneğin<strong> Entity Framework</strong>’ de. Bakalım bu günkü esintiler bizi nerelere götürecek?</p>
<p><strong>Entity Framework</strong> geliştirilmeye ve bünyesine yeni özellikler dahil edilmeye devam etmekte. Ancak son gelişmelerden bir tanesi oldukça önemli sanırım. O da artık <strong>Entity Framework</strong>’ ün tamamen harici bir <strong>NuGet</strong> paketi olarak kullanılacağı. Bir başka deyişle <strong>.Net Framework</strong>’ ün bir parçası olmaktan çıkartılmış ve <strong>Codeplex</strong> üzerinden yürür duruma gelmiş. Son bilgileri göre <strong>EF 6x </strong>verisyonları <strong>.Net 4.0 </strong>ve üstü için kullanılabiliyor. Ayrıca Visual Studio 2010 ve sonrası IDE’ ler de ele alındığını da belirtelim. <em>(Standalone bir kütüphane olarak değerlendirebileceğimiz Entity Framework ile ilişkili son gelişmeleri <a href="http://entityframework.codeplex.com/" target="_blank">Codeplex üzerindeki adreslerinden</a> takip etmekte yarar var. Hatta yanılmıyorsam projeye Contributor olarak katılmanız bile mümkün olabilir)</em></p>
<h1>CUD Operasyonlarında Stored Procedure Kullanımı ve Interceptors</h1>
<p>Tabi bu önemli değişiklik dışında dikkatimiz çeken başka yenilikler de var. Örneğin artık <strong>context</strong> üzerinden çalıştırılan sorgu komutlarının yakalanması mümkün. Aslında basit bir kesme/araya girme mekanizmasından bahsediyoruz. Ya da veri ekleme, güncelleme ve silme gibi <strong>CUD<em>(CreateUpdateDelete)</em> </strong>operasyonlarına ait komutlar çalıştırılırken<em><strong>(Executing)</strong></em> ve çalıştırıldıktan sonra<em><strong>(Executed)</strong></em> araya girebilme yeteneğinden. Bu noktada çok doğal olarak devreye bir <strong>arayüz<em>(Interface)</em></strong> tipinin girdiğini ve çalışma zamanı <strong>Context</strong>’ ine bu arayüz sayesinde yeni bir davranış biçimi kazandırılabildiğini ifade edebiliriz.</p>
<blockquote>
<p>Yine de “Bu sistem sıfırdan tasarlansaydı, geliştiricilerin belirli kurallar çerçevesinde genişletmeler yapabilmeleri nasıl sağlanırdı?” sorusuna cevap bulmaya çalışarak kendinizi geliştirebilirsiniz.</p>
</blockquote>
<p><strong>Interceptor’</strong> lar dışında kayda değer bir diğer özellikle de <strong>CUD</strong> operasyonlarının veritabanı şemasının oluşturulması noktasında <strong>Stored</strong> <strong>Procedure</strong> olarak da belirlenebilmesi. Buna göre bir <strong>Entity</strong> için söz konusu olan tipik <strong>Insert</strong>, <strong>Update</strong> ve <strong>Delete</strong> operasyonlarının bire Stored Procedure şeklinde ele alınabilmesi de mümkün hale gelmekte. <em>(Burada iki yaklaşım olduğunu ifade edebiliriz. CUD operasyonunu var olan bir <strong>Stored</strong> <strong>Procedure</strong> ile eşleştirme veya sıfırdan üretilmesini sağlama)</em></p>
<p>Biz bu yazımızda söz konusu iki önemli özelliği basit bir <strong>Console</strong> uygulaması üzerinden yüzeysel olarak incelemeye çalışacağız.</p>
<h1>Senaryo</h1>
<p>Elbette basit bir senaryo üzerinden ilerlenmesinde yarar var. İçinde sadece <strong>Product</strong> isimli bir tip barındıran <strong>DbContext</strong> türevinde, <strong>CUD</strong> işlemlerinin <strong>Stored</strong> <strong>Procedure</strong> olarak ele alınmasını sağlayacağız. Bunun haricinde çalışma zamanında her hangibir <strong>Stored</strong> <strong>Procedure</strong> çağrımı söz konusu olursa, <strong>NLog</strong> aracından faydalanarak, yordama ait o anki parametre değerlerinin <strong>Console</strong> ekranına yazdırılmasını sağlayacağız.</p>
<h1>Ön Hazırlıklar <em>(Entity Framework ve NLog)</em></h1>
<p>Senaryomuzda iki önemli <strong>NuGet</strong> paketine yer veriyor olacağız. <strong>Entity</strong> <strong>Framework</strong> ve <strong>NLog</strong>. Makalenin yazıldığı tarih itibariyle örnekte <strong>EF</strong>’ in <strong>Stable</strong> sürümlerinden <strong>6.1 </strong>kullanılmıştır. <strong>NLog</strong> paketini ise Log yazma mekanizması için kullanacağız.</p>
<p>EF 6.1 Paketinin eklenmesi;</p>
<p><a href="https://buraksenyurt.com/pics/ef61_1_1.png"><img style="margin: 4px 0px; display: inline;" title="ef61_1" src="/pics/ef61_1_thumb_1.png" alt="ef61_1" width="420" height="278" /></a></p>
<p>NLog Paketlerinin eklenmesi;</p>
<p><a href="https://buraksenyurt.com/pics/ef61_2_1.png"><img style="margin: 4px 0px; display: inline;" title="ef61_2" src="/pics/ef61_2_thumb_1.png" alt="ef61_2" width="420" height="278" /></a></p>
<p><strong>NLog</strong> paketlerinden <strong>NLog</strong> <strong>Configuration’</strong> ı eklemeyi unutmayalım. Bu sayede <strong>NLog.config</strong> dosyası içerisinde <strong>intellisense</strong> özelliğinden yararlanabileceğiz.</p>
<h1>Konfigurasyon İçerikleri</h1>
<p>Normal şartlarda <strong>Entity</strong> <strong>Framework</strong> varsayılan olaral local veritabanını, <strong>Context</strong> adını baz alarak oluşturmakta ve kullanmaktadır. Ancak bilindiği üzere <strong>connectionStrings</strong> kımsında belirtilen bağlantı cümleciğinden yararlanılması da sağlanabilir. Örnekte makinede yer alan yerel <strong>SQL</strong> sunucusu kullanılmıştır. Buna göre <strong>ShopContext</strong> isimli <strong>DbContext</strong> türevi için, <strong>.</strong> ile belirtilen <strong>SQL</strong> sunucusu üzerinde <strong>YourShop</strong> isimli bir veritabanı üretilecektir. İşte <strong>App.config</strong> içeriği;</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add
name="ShopContext"
connectionString="Data Source=.;Initial Catalog=YourShop;Integrated Security=True;MultipleActiveResultSets=True"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration></pre>
<p><strong>NLog.config</strong> dosya içeriği ise aşağıdaki gibi oluşturulabilir. Burada <strong>target</strong> elementinde belirtilen nitelik değerlerine göre, <strong>log</strong>’ ların renkli formatta <strong>Console</strong> ekranına yazdırılması sağlanmaktadır. Diğer yandan <strong>layout</strong> niteliğinde belirtilen değere göre <strong>log</strong> mesajının başına uzun formatta bir tarih bilgisi eklenecektir<em>($ ile başlayan kelimelerin NLog çalışma zamanı için anlamlaştırılabilen birer komut olduğunu ifade edebiliriz)</em> <strong>rules</strong> elementi içerisinde ise minimum <strong>Trace</strong> seviyesinde olmak üzere her tür bilginin log olarak yazılacağı belirtilmektedir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target xsi:type="ColoredConsole" name="c"
layout="${longdate} : ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="c" />
</rules>
</nlog></pre>
<h1>Kod</h1>
<p>Artık kod tarafının inşasına başlanabilir. Temel olarak aşağıdaki <strong>sınıf çizelgesinde<em>(Class Diagram)</em></strong> yer alan şema kodlanmıştır.</p>
<p><a href="https://buraksenyurt.com/pics/ef61_5.png"><img style="margin: 4px 0px; display: inline;" title="ef61_5" src="/pics/ef61_5_thumb.png" alt="ef61_5" width="617" height="664" /></a></p>
<p>Tipik olark <strong>Code-First</strong> yaklaşımına uygun olacak şekilde bir <strong>POCO</strong> tipi<em>(Product)</em> ve buna ait <strong>DbSet</strong> içeriğinin konuşlandırıldığı<em>(Products özelliği)</em> bir <strong>DbContext</strong> türevi söz konusudur. Dikkate değer kısımlar ise, <strong>EF</strong> için konfigurasyon ayarlarının kod tarafında da ele alınmasını sağlayan, <strong>DbConfiguration</strong> türevli <strong>ShopContextConfig</strong> ve <strong>IDbCommandInterceptor</strong> arayüzünü uygulamış olan <strong>StoredProcedureInterceptor</strong> sınıflarıdır.</p>
<p><strong>StoredProcedureInterceptor</strong> sınıfı, <strong>IDbInterceptor</strong> arayüzünden gelen <strong>NonQueryExecuted</strong>, <strong>NonQueryExecuting</strong>, <strong>ReaderExecuted</strong>, <strong>ReaderExecuting</strong>, <strong>ScalarExecuted</strong>, <strong>ScalarExecuting</strong> metodlarını uygulamak durumundadır. Bu metodların tamamı o anki işleyişe kaynak olan <strong>DbCommand</strong> nesne örneği ile güncel <strong>Context</strong> içeriğini parametre olarak alır. Dolayısıyla, <strong>CUD</strong> operasyonlarının çalıştırıldığı veya tamamlandığı noktalarda ilgili nesne örneklerinin durumları ele alınabilir. Özetle söz konusu operasyonlar sırasında bu metodları kullanarak araya girilebileceğini ifade edebiliriz.</p>
<p><strong>DbConfiguration</strong> türevli olan <strong>ShopConfig</strong> sınıfının bu senaryodaki temel görevi, <strong>Interceptor</strong> tiplerinin çalışma zamanına bildirimini yapmaktır. Çok doğal olarak <strong>IDbInterceptor</strong> arayüzünü uygulayan tiplerin bir şekilde çalışma zamanına bildirilmesi gerekmektedir.</p>
<p>Gelelim kodun detaylarına.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Linq;
using NLog;
namespace EF6_NewFeatures
{
class Program
{
static void Main(string[] args)
{
using (ShopContext bilbosShop = new ShopContext())
{
bilbosShop.Products.Add(new Product { Title = "Platsik Tabak", Quantity = 10, UnitPrice = 1.05M });
bilbosShop.Products.Add(new Product { Title = "Metal Kaşık", Quantity = 5, UnitPrice = 12 });
bilbosShop.Products.Add(new Product { Title = "Tahta Bıçak", Quantity = 15, UnitPrice = 13.49M });
bilbosShop.SaveChanges();
var products = from p in bilbosShop.Products
where p.UnitPrice < 2
select p;
foreach (var p in products)
{
p.UnitPrice+= 0.5M;
}
bilbosShop.SaveChanges();
}
}
}
class Product
{
public int ProductID { get; set; }
public string Title { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
}
class ShopContext
:DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().MapToStoredProcedures(p =>
{
p.Insert(s => s.HasName("sp_InsertProduct"));
p.Update(s => s.HasName("sp_UpdateProduct"));
});
base.OnModelCreating(modelBuilder);
}
}
class StoredProcedureInterceptor
: IDbCommandInterceptor
{
Logger logger = LogManager.GetCurrentClassLogger();
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogCurrentProcedureState(command, "Non Query Executed");
}
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
LogCurrentProcedureState(command, "Non Query Executing");
}
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogCurrentProcedureState(command, "Reader Executed");
}
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
LogCurrentProcedureState(command,"Reader Executing");
}
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}
private void LogCurrentProcedureState(DbCommand command, string Location)
{
if (command.CommandType == CommandType.StoredProcedure)
{
logger.Trace(string.Format("{0}->{1}", command.CommandText, Location));
foreach (DbParameter parameter in command.Parameters)
{
logger.Info(string.Format("{0}{1}", parameter.ParameterName.PadLeft(20), parameter.Value.ToString().PadLeft(30)));
}
}
}
}
class ShopContextConfig
:DbConfiguration
{
public ShopContextConfig()
{
AddInterceptor(new StoredProcedureInterceptor());
}
}
}</pre>
<h1>Detaylar</h1>
<p><strong>ShopContext</strong> sınıfı içerisinde ezilen <strong>OnModelCreating</strong> metodunda <strong>MapToStoredProcedures</strong> isimli fonksiyonun kullanıldığı görülmektedir. <strong>Product</strong> isimli <strong>Entity</strong> tipi için çalıştırılan metodun <strong>lambda</strong> operatörünün kullanıldığı içeriğinde ise <strong>Insert</strong> ve <strong>Update</strong> fonksiyonları için bire <strong>Stored</strong> <strong>Procedure</strong> adı verildiği görülebilir. Buna göre <strong>Product</strong> tipinin <strong>insert</strong> operasyonu için <strong>sp_InsertProduct</strong>, <strong>Update</strong> operasyonu için <strong>sp_UpdateProduct</strong> isimli yordamlar oluşturulacak ve kullanılacaktır. Dikkat edileceği üzere <strong>Delete</strong> operasyonu için bir bildirim de <span style="text-decoration: underline;">bulunulmamıştır</span>. Ancak <strong>MapToStroredProcedures</strong> metodu otomatik olarak <strong>delete</strong> operasyonu için <strong>Product_Delete</strong> şeklinde bir yordam üretecektir.</p>
<p>Senaryoya göre, <strong>StoredProcedureInterceptor</strong> sınıfının <strong>NonQueryExecuting</strong>, <strong>NonQueryExecuted</strong>, <strong>ReaderExecuting</strong> ve <strong>ReaderExecuted</strong> isimli metodları değerlendirilmiştir. Metodlar <strong>LogCurrentProcedureState</strong> isimli bir iç fonksiyondan yararlanmaktadır. Söz konusu metod <strong>DbCommand</strong> örneğinin tipine göre hareket etmektedir. Eğer söz konusu komut bir <strong>Stored</strong> <strong>Procedure</strong> ise o anki parametre değerleri ekrana yazdırılacaktır.</p>
<p><strong>ShopContextConfig</strong> sınıfı, <strong>DbConfiguration</strong> sınıfından türetilmiştir. Bu türetme otomatik olarak çalışma zamanınca algınır ve yapıcı metod içerisinde belirtilen bazı ayarların yürütülmesi sağlanır. Burada <strong>AddInterceptor</strong> isimli metoddan yararlanılmaktadır. İlgili fonksiyon tahmin edileceği üzere parametre olarak yeni bir <strong>StoredProcedureInterceptor</strong> örneğini alır. Yapıcı metod içerisinde ele alınabilecek farklı konfigurasyon ayarlamaları da söz konusudur. <em>(Bunların neler olduğunu keşfetmek için this anahtar kelimesini yazıp noktaya basmanızı önerebilirim)</em> </p>
<blockquote>
<p>DbConfiguration türevli tipler sayesinde, DbContext örneklerinin konfigurasyon bazındaki ayarları kod üzerinden yapılabilir.</p>
</blockquote>
<h1>Çalışma Zamanı Sonuçları</h1>
<p>Uygulama çalıştırıldığında aşağıdaki ekran görüntüsünde yer alan sonuçların elde edildiği görülebilir. Aslında 3 adet Product örneği eklenmekte ve birim fiyatı 2 birimin altında olanlar için bir güncelleme yapılmaktadır. Tabi örnekteki amaç bu <strong>CUD</strong> işlemlerinin oluştuğu anlarda parametre değerlerinin yakalanmasıdır.</p>
<p><a href="https://buraksenyurt.com/pics/ef61_3.png"><img style="margin: 4px 0px; display: inline;" title="ef61_3" src="/pics/ef61_3_thumb.png" alt="ef61_3" width="588" height="451" /></a></p>
<p>Görüldüğü üzere <strong>Main</strong> metodu içerisinde gerçekleştirilen yeni ürün ekleme ve güncelleme işlemlerine karşılık yürütülen <strong>Stored</strong> <strong>Procedure</strong> çağrıları yakalanmış ve o andaki parametre değerleri <strong>Console</strong> ekranına yazdırılmıştır.</p>
<h1>Peki ya Veritabanı Durumu?</h1>
<p>Veritabanına bakılırsa <strong>Products </strong>isimli bir tablonun ve <strong>CUD</strong> operasyonlarına karşılık ilgili <strong>stored procedure</strong>’ lerin oluşturulduğu gözlemlenir.</p>
<p><a href="https://buraksenyurt.com/pics/ef61_4.png"><img style="margin: 4px 0px; display: inline;" title="ef61_4" src="/pics/ef61_4_thumb.png" alt="ef61_4" width="562" height="398" /></a></p>
<p>Yordamlara ait Script’ ler ise aşağıdaki gibidir.</p>
<p>Product_Delete isimli yordam için;</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">USE [YourShop]
GO
/****** Object: StoredProcedure [dbo].[Product_Delete] Script Date: 04/09/2014 11:09:01 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Product_Delete]
@ProductID [int]
AS
BEGIN
DELETE [dbo].[Products]
WHERE ([ProductID] = @ProductID)
END</pre>
<p><strong>Delete</strong> yordamında beklendiği gibi <strong>ProductID’</strong> nin parametre olarak değerlendirildiği bir script söz konusudur. Nitekim <strong>Product</strong> tipinin <strong>ProductID</strong> özelliği tablo tarafında <strong>Primary</strong> <strong>Key</strong> alandır.</p>
<p>sp_InsertProduct isimli yordam için;</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">USE [YourShop]
GO
/****** Object: StoredProcedure [dbo].[sp_InsertProduct] Script Date: 04/09/2014 11:08:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_InsertProduct]
@Title [nvarchar](max),
@UnitPrice [decimal](18, 2),
@Quantity [int]
AS
BEGIN
INSERT [dbo].[Products]([Title], [UnitPrice], [Quantity])
VALUES (@Title, @UnitPrice, @Quantity)
DECLARE @ProductID int
SELECT @ProductID = [ProductID]
FROM [dbo].[Products]
WHERE @@ROWCOUNT > 0 AND [ProductID] = scope_identity()
SELECT t0.[ProductID]
FROM [dbo].[Products] AS t0
WHERE @@ROWCOUNT > 0 AND t0.[ProductID] = @ProductID
END</pre>
<p><strong>Insert</strong> operasyonunda yeni ürünün eklenmesini takiben, <strong>ProductID</strong> için oluşturulan otomatik <strong>Identity </strong>değerinin geri döndürüldüğü gözlemlenmektedir.</p>
<p>sp_UpdateProduct için;</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">USE [YourShop]
GO
/****** Object: StoredProcedure [dbo].[sp_UpdateProduct] Script Date: 04/09/2014 11:08:23 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_UpdateProduct]
@ProductID [int],
@Title [nvarchar](max),
@UnitPrice [decimal](18, 2),
@Quantity [int]
AS
BEGIN
UPDATE [dbo].[Products]
SET [Title] = @Title, [UnitPrice] = @UnitPrice, [Quantity] = @Quantity
WHERE ([ProductID] = @ProductID)
END</pre>
<p>Update ifadesinde aynen Delete ifadesinde olduğu gibi ProductID isimli Primary Key alandan yararlanılmaktadır.</p>
<blockquote>
<p>Title isimli alanın kullanıldığı parametrelerin nvarchar(max) tipinden tanımlandığı gözden kaçmamalıdır. Bu normaldir nitekim Title alanı Products tablosu içinde bu şekilde oluşturulmuştur. Halbuki bu alanın örneğin 50 karakter uzunluğunda olması daha uygun olabilir. Code-First yaklaşımını kullandığımız bu örnek senaryoda bu kriteri nasıl sağlarsınız? İşte size güzel bir antrenman sorusu.</p>
</blockquote>
<h1>Sonuç</h1>
<p>Dikkat edileceği üzere <strong>CUD</strong> operasyonlarının icrası noktasında araya girerek bir takım iş kurallarının işletilmesi mümkündür. Hatta örnek senaryoda görüldüğü gibi <strong>Log’</strong> lamanın daha kural bazlı işlenmesi için bu kesmeler ideal olabilir. Diğer yandan <strong>CUD</strong> operasyonlarının <strong>Stored</strong> <strong>Procedure</strong> olarak inşa edilebilmesi, ilgili fonksiyonelliklerin veritabanı tarafında birer yordam nesnesi olarak değerlendirilebilmesi anlamına gelmektedir. Ayrıca parametre yapıları doğru ise <strong>DB</strong> tarafında var olan <strong>SP’</strong> lerin <strong>map</strong> edilmesi de mümkündür. Bu durumda <strong>Db</strong> tarafındaki <strong>SP’</strong> ler içerisinde yer alan ve DB’ ye özgü bir takım ifadelerin bu basit <strong>CUD</strong> operasyonları sırasında değerlendirilebilmesi de söz konusudur.</p>
<p><strong>Entity</strong> <strong>Framework</strong> tarafında geliştirmeler devam etmektedir. Codeplex üzerinden izlemeye devam ediyor olacağız. Bakalım baharın geldiği şu günlerde daha ne gibi esintilerle karşılacaşacağız. Böylece geldik bir makalemizin daha sonunda. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2014-04-08T11:09:00+00:00entity frameworknew features.netef 6.0bsenyurtEntity Framework geliştirilmeye ve bünyesine yeni özellikler dahil edilmeye devam etmekte. Ancak son gelişmelerden bir tanesi oldukça önemli sanırım. O da artık Entity Framework’ ün tamamen harici bir NuGet paketi olarak kullanılacağı. Bir başka deyişle .Net Framework’ ün bir parçası olmaktan çıkartılmış ve Codeplex üzerinden yürür duruma gelmiş. Son bilgileri göre EF 6x verisyonları .Net 4.0 ve üstü için kullanılabiliyor. Ayrıca Visual Studio 2010 ve sonrası IDE’ ler de ele alındığını da belirtelim.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=1209deda-a523-4dc9-93cc-d96d9c5232033https://buraksenyurt.com/trackback.axd?id=1209deda-a523-4dc9-93cc-d96d9c523203https://buraksenyurt.com/post/Entity-Framework-6-e28093Yeni-Esintiler#commenthttps://buraksenyurt.com/syndication.axd?post=1209deda-a523-4dc9-93cc-d96d9c523203https://buraksenyurt.com/post/Entity-Framework-Code-First-icin-Calculated-Fields-KullanimiEntity Framework Code-First için Calculated Fields Kullanımı2013-02-08T05:15:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/hot-water-migration.jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="hot-water-migration" src="/pics/hot-water-migration_thumb.jpg" alt="hot-water-migration" width="240" height="189" align="right" /></a>Merhaba Arkadaşlar,</p>
<p>Genellikle göç etmek gibi anlamlarda kullanılan <strong>Migrate </strong>kelimesinin yazılım dünyasındaki karşılığını düşündüğümüzde, elbetteki yandaki fotoğrafta yer alan ve bir birlerinin akvaryumuna atlayan balıklar gelmeyecektir/gelmemelidir.</p>
<p>Ancak <strong>Entity Framework Code-First </strong>yaklaşımı ve <strong>Calculated Fields </strong>kavramını göz önüne getirdiğimizde, <strong>Migration </strong>kelimesini ciddi manada düşünmemiz gerekebilir. Nasıl mı? Haydi okumaya devam <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_134.png" alt="Winking smile" /></p>
<p>Hesaplanmış alanlar<em>(Calculated Fields/Columns)</em> veritabanı programcılığında sık kullanılan özelliklerden birisidir. Bu alanların içeriği genellikle tablonun diğer alanları kullanılarak bir hesaplama sonucu üretilir. Söz gelimi personel verilerinin tutulduğu bir tablodaki FirstName ve LastName alanlarının değerleri birleştirilerek, bir <strong>Calculated Field</strong> oluşturulması mümkündür. Peki bu desteği <strong>Entity Framework Code-First</strong> yaklaşımında nasıl değerlendirebiliriz?</p>
<p>Bildiğiniz üzere<strong> Entity Framework Code-First</strong> yaklaşımında, veritabanı nesnelerinin tasarımları <strong>POCO(Plain Old CRL Object)</strong> tipleri üzerinden gerçekleştirilmektedir. Dolayısıyla<strong> Calculated Field</strong> şeklinde düşünülmesi gereken bir özelliğin veritabanı tarafına nasıl yansıtılacağı kafalarda bir soru işareti oluşturmaktadır. Pek tabi bunun için de bir <strong>nitelik(attribute)</strong> desteği sunulmuş olabilir ki öyledir. <strong>DatabaseGenerated </strong>niteliğinde <strong>DatabaseGeneratedOption.Computed</strong> <strong>enum</strong> sabiti değerini kullanarak, istenilen hesaplanabilir alan bildirimlerini yaptırabiliriz. Acaba durum gerçekten böyle midir? <img class="wlEmoticon wlEmoticon-whome" style="border-style: none;" src="/pics/wlEmoticon-whome.png" alt="Who me?" /></p>
<p>Dilerseniz basit bir örnek üzerinden hareket ederek konuyu incelemeye çalışalım. İlk etapta aşağıdaki sınıf çizelgesinde<em>(Class Diagram)</em> yer alan tipleri geliştirdiğimizi düşünelim. Senaryomuzdaki başrol oyuncuları, <strong>Shop</strong> isimli <strong>Context</strong> tipi ve <strong>Product </strong>sınıfının <strong>TotalPrice </strong>özelliğidir.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_3.png"><img style="margin: 4px 0px; display: inline;" title="efcf_3" src="/pics/efcf_3_thumb.png" alt="efcf_3" width="284" height="326" /></a></p>
<p><strong>Product</strong> isimli örnek <strong>POCO(Plain Old CLR Object)</strong> tipi;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ComponentModel.DataAnnotations.Schema;
namespace HowTo_CalculatedFields
{
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int ListPrice { get; set; }
public int Quantity { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int TotalPrice {
get;
private set;
}
}
}</pre>
<p><strong>DbContext</strong> türevli Context tipi;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Entity;
namespace HowTo_CalculatedFields
{
public class Shop
:DbContext
{
public DbSet<Product> Products{ get; set; }
}
}</pre>
<p><strong>Product</strong> tipi içerisinde yer alan <strong>TotalPrice</strong> özelliğine dikkat edelim. Bu özellik içerisinde ürünün fiyatı ve miktarından yararlanılarak gerçekleştirilen bir hesaplama işlemi söz konusudur. Bunun veritabanı tarafına da yansıtılması için <strong>DatabaseGenerated</strong> niteliğinden yararlanılmaktadır. Peki çalışma zamanı bu durumu anlayabilecek midir?</p>
<blockquote>
<p>Örneğimizde Code-First yaklaşımına istinaden config dosyasında aşağıdaki bağlantı bilgisini kullanmayı tercih ettim. Herhangibir bilgi ifade etmediğimizde SQL Express sürümü üzerinde bir veritabanı oluşturulmaya çalışıldığını hatırlatmak isterim. Diğer önemli bir nokta da DbContext türevli sınıf adı ile ConnectionString elementinin name niteliğinin değerlerinin aynı olmasıdır. Bu sayede çalışma zamanı Shop veritabanı için gerekli bağlantı bilgisini bulabilir.</p>
<p><?xml version="1.0"?> <br /><configuration> <br /> <configSections> <br /> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/> <br /> </configSections> <br /> <entityFramework> <br /> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"/> <br /> </entityFramework> <br /> <connectionStrings> <br /> <add <br /> name="Shop" <br /> connectionString="data source=localhost;database=Shop;integrated security=true" <br /> providerName="System.Data.SqlClient"/> <br /> </connectionStrings> <br /><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration></p>
</blockquote>
<p><strong>Program.cs</strong> içeriğini aşağıdaki şekilde kodlayarak senaryomuza devam edelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Linq;
namespace HowTo_CalculatedFields
{
class Program
{
static void Main(string[] args)
{
using (Shop context = new Shop())
{
Product hpKeyboard = new Product
{
Name="HP 102 Tuş Kablosuz Klavye",
ListPrice=35,
Quantity=125
};
context.Products.Add(hpKeyboard);
context.SaveChanges();
var finded = (from k in context.Products
where k.Name == "HP 102 Tuş Kablosuz Klavye"
select k)
.FirstOrDefault();
Console.WriteLine(finded.TotalPrice);
}
}
}
}</pre>
<p><strong>Shop</strong> <strong>context</strong> tipinin örneklenmesinin ardından bir Product nesnesi üretilmektedir. Dikkat edileceği üzere <strong>identity</strong> alan olarak <strong>set</strong> edilen <strong>ProductId</strong> ve <strong>Calculated Field</strong> olması planlanan <strong>TotalPrice</strong> için bir atama işlemi söz konusu değildir. Beklentimiz yeni <strong>Product</strong>, <strong>context</strong> üzerine eklendiğinde <strong>TotalPrice</strong> alanınında otomatik olarak hesaplanmış olmasıdır. Lakin bu aşamaya kadar ilerleyemeyiz bile. <a href="https://buraksenyurt.com/pics/efcf_1.png"><img style="margin: 4px 0px; display: inline;" title="efcf_1" src="/pics/efcf_1_thumb.png" alt="efcf_1" width="620" height="375" /></a></p>
<p>Dikkat edileceğiz üzere <strong>SaveChanges</strong> metoduna yapılan çağrı sonrasında bir çalışma zamanı istisnası<em>(Runtime Exception)</em> oluşmuştur. Söylenene göre <strong>TotalPrice</strong> alanı <strong>null</strong> değer içeremez. Aslında bu mesajı doğrudan Calculated Field ile ilişkili değildir. Yine de veritabanı tarafına baktığımızda şöyle bir durum oluştuğunu gözlemleyebiliriz; <strong>Shop</strong> isimli veritabanı üretilmiş, <strong>Products</strong> isimli tablo oluşturulmuş ve hatta içerisine <strong>TotalPrice</strong> isimli alan da dahil edilmiştir. Hımmm...Ne var ki <strong>TotalPrice</strong> kolonu <strong>Calculate Field</strong> haline gelmemiştir.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_4.png"><img style="margin: 4px 0px; display: inline;" title="efcf_4" src="/pics/efcf_4_thumb.png" alt="efcf_4" width="357" height="381" /></a></p>
<p>Peki ya çözüm? <img class="wlEmoticon wlEmoticon-idontknowsmile" style="border-style: none;" src="/pics/wlEmoticon-idontknowsmile.png" alt="I don't know smile" /></p>
<p>Neyseki elimizin altında <strong>migration</strong> diye bir kabiliyet bulunmakta. Şu anda var olan veritabanı yapısını biraz değiştirip, <strong>TotalPrice</strong> alanı için de bir müdahalede bulunmamız gerekecek. <em>(Hatta <strong>Name</strong> alanının boyutuna bir dokunuş yaparsak hiç de fena olmaz <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_134.png" alt="Winking smile" />)</em></p>
<p>Şimdi <strong>Migration</strong> özelliğini etkinleştirip yeni bir <strong>Migration</strong> setini projeye dahil ediyor olacağız. Bunun için <strong>Package</strong> <strong>Manager</strong> <strong>Console</strong> penceresinden sırasıyla <strong>Enable-Migrations</strong> ve <strong>Add-Migration</strong> komutlarını çağıralım. Aşağıdaki gibi.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_2.png"><img style="margin: 4px 0px; display: inline;" title="efcf_2" src="/pics/efcf_2_thumb.png" alt="efcf_2" width="428" height="734" /></a></p>
<p><strong>AddTotalPriceCalculateFields</strong> olarak adlandırdığımız <strong>Migration</strong> sınıfının içeriğinde yer alan <strong>Up</strong> ve <strong>Down</strong> metodlarını ise şu şekilde düzenleyebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace HowTo_CalculatedFields.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddTotalPriceCalculatedFields
: DbMigration
{
public override void Up()
{
DropTable("dbo.Products");
CreateTable(
"dbo.Products",
c => new
{
ProductId = c.Int(nullable: false, identity: true),
Name = c.String(maxLength:50),
ListPrice = c.Int(nullable: false),
Quantity = c.Int(nullable: false)
})
.PrimaryKey(t => t.ProductId);
Sql("ALTER TABLE dbo.Products ADD [TotalPrice] as ([ListPrice] * [Quantity])");
}
public override void Down()
{
DropTable("dbo.Products");
}
}
}</pre>
<p>Aslında iki noktaya dokunduk. İlk olarak <strong>TotalPrice</strong> alaının eklenmesi için herhangibir işlem yapmadığımızı görüyoruz. İkinci olarak da bir <strong>T-SQL</strong> ifadesinin çalıştırılması için gerekli metod çağrısında bulunduk. <strong>Sql</strong> Metod çağrısına dikkat edilecek olursa <strong>Calculated</strong> <strong>Field</strong> için gerekli olan T-SQL ifadesini içerdiğini görebiliriz. Kısacası tablo <strong>Create</strong> edildikten sonra bir <strong>Alter</strong> işlemini bilinçli olarak uygulatıyor ve hesaplanabilir alanın bildirilmesini sağlıyoruz.</p>
<p>Artık veritabanını manuel olarak güncelletebiliriz. Bu güncelleme işlemi için <strong>Package Manager Console</strong> üzerinden Update-Database komutunu göndermemiz yeterli olacaktır<img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_134.png" alt="Winking smile" /> </p>
<blockquote>
<p>Verbose anahtarını kullanmamızın tek sebebi, veritabanına doğru giden T-SQL ifadelerini görmektir.</p>
</blockquote>
<p><a href="https://buraksenyurt.com/pics/efcf_5.png"><img style="margin: 4px 0px; display: inline;" title="efcf_5" src="/pics/efcf_5_thumb.png" alt="efcf_5" width="544" height="460" /></a></p>
<p>Bu adımdan sonra veritabanına gidip <strong>Products</strong> tablosuna baktığımızda, gerçektende <strong>TotalPrice</strong> için bir <strong>Calculated T-SQL</strong> ifadesinin yazılmış olduğunu görebiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_6.png"><img style="margin: 4px 0px; display: inline;" title="efcf_6" src="/pics/efcf_6_thumb.png" alt="efcf_6" width="338" height="355" /></a></p>
<p>Üstelik çalışma zamanında bir ürünü çektiğimizde, miktar ve birim fiyata göre <strong>TotalPrice</strong> özelliğinin de veritabanından hesaplanarak getirildiğini görebiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_7.png"><img style="margin: 4px 0px; display: inline;" title="efcf_7" src="/pics/efcf_7_thumb.png" alt="efcf_7" width="334" height="168" /></a></p>
<p>Herşey buraya kadar iyi gitti diyebiliriz. Lakin ufak bir sorunumuz daha var. <img class="wlEmoticon wlEmoticon-laughingoutloud" style="border-style: none;" src="/pics/wlEmoticon-laughingoutloud_1.png" alt="Laughing out loud" /> Eğer <strong>Quantity</strong> veya <strong>ListPrice</strong> değerlerinde, nesne örneği üzerinden değişiklik yaparsak, bu durumda <strong>Calculated</strong> <strong>Field</strong> beklediğimiz gibi bir davranış göstermeyecektir. Aşağıdaki ekran görüntüsünde yer alan kod parçasını dikkate alalım.</p>
<p><a href="https://buraksenyurt.com/pics/efcf_8.png"><img style="margin: 4px 0px; display: inline;" title="efcf_8" src="/pics/efcf_8_thumb.png" alt="efcf_8" width="430" height="286" /></a></p>
<p>Senaryoda ürün eklendikten sonraki durumda <strong>Calculated</strong> <strong>Field</strong> alanının hesaplanarak geldiği görülmektedir. Yani ilk eklemeden sonra gerçekleştirilen <strong>LINQ</strong> ifadesine göre TotalPrice için SQL tarafındaki hesaplama devreye girmiştir. Ancak bellek üzerinde kalan <strong>Product</strong> nesne örneğinin <strong>Quantity</strong> veya <strong>ListPrice</strong> alanlarında bir değişiklik yapıldığında, bu çok doğal olarak <strong>TotalPrice'</strong> a yansımayacaktır.</p>
<p>Bu durum çok doğal olarak veritabanına gidilmeden yapılan nesne örneği bazlı özellik güncellemelerinde doğru verinin gösterilemeyeceği anlamına gelir ki bu da pek istemediğimiz bir durumdur. Soruna <strong>Product</strong> tipi içerisindeki <strong>TotalPrice</strong> özelliği üzerinden müdahalede bulunarak çözüm getirebiliriz. Aynen aşağıdaki kod parçasında görüldüğü gibi;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ComponentModel.DataAnnotations.Schema;
namespace HowTo_CalculatedFields
{
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int ListPrice { get; set; }
public int Quantity { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int TotalPrice
{
get
{
return ListPrice * Quantity;
}
private set // get bloğunu açtığımız için aşağıdaki bloğu boş olsa bile açmak mecburiyetindeyiz.
{
}
}
}
}</pre>
<p>İşte şimdi oldu <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_134.png" alt="Winking smile" /></p>
<p><a href="https://buraksenyurt.com/pics/efcf_9.png"><img style="margin: 4px 0px; display: inline;" title="efcf_9" src="/pics/efcf_9_thumb.png" alt="efcf_9" width="288" height="79" /></a></p>
<p>Böylece geldik bir yazımızın daha sonuna. Bu makalemizde<strong> Code-First</strong> yaklaşımının kullanıldığı senaryolarda, biraz da veritabanı tarafına özgü olan <strong>Calculated</strong> <strong>Field’</strong> ların nasıl etkin hale getirilebileceğini bir kaç küçük hile ile incelemeye çalıştık. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/pics/2012%2f9%2fHowTo_CalculatedFields.zip">HowTo_CalculatedFields.zip (2,60 mb)</a></p>2013-02-08T05:15:00+00:00entity frameworkentity framework 4.5t-sqlmigrationcode-firstpococ#ado.netalterbsenyurtBildiğiniz üzere Entity Framework Code-First yaklaşımında, veritabanı nesnelerinin tasarımları POCO(Plain Old CRL Object) tipleri üzerinden gerçekleştirilmektedir. Dolayısıyla Calculated Field şeklinde düşünülmesi gereken bir özelliğin veritabanı tarafına nasıl yansıtılacağı kafalarda bir soru işareti oluşturmaktadır. Pek tabi bunun için de bir nitelik(attribute) desteği sunulmuş olabilir ki öyledir. DatabaseGenerated niteliğinde DatabaseGeneratedOption.Computed enum sabiti değerini kullanarak, istenilen hesaplanabilir alan bildirimlerini yaptırabiliriz. Acaba durum gerçekten böyle midir?https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=e77f0afe-3b47-4125-ad86-010ac77cadca3https://buraksenyurt.com/trackback.axd?id=e77f0afe-3b47-4125-ad86-010ac77cadcahttps://buraksenyurt.com/post/Entity-Framework-Code-First-icin-Calculated-Fields-Kullanimi#commenthttps://buraksenyurt.com/syndication.axd?post=e77f0afe-3b47-4125-ad86-010ac77cadcahttps://buraksenyurt.com/post/Entity-Framework-6-Code-First-ConventionEntity Framework 6 – Code First için Convention Nedir?2012-12-12T05:15:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/themask.jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="themask" src="/pics/themask_thumb.jpg" alt="themask" width="218" height="240" align="right" /></a>Merhaba Arkadaşlar,</p>
<p><strong>Entity Framework</strong> takımı aldı başını gidiyor. Kim durduracak onları. Onlarda <strong>The Mask</strong> filmindeki karakter gibi <strong>“Somebody stop me!”</strong> demiyor ki <img class="wlEmoticon wlEmoticon-justkidding" style="border-style: none;" src="/pics/wlEmoticon-justkidding.png" alt="Just kidding" /> </p>
<p>Aslında bakarsanız olaylar bana göre, Microsoft geliştirici takımlarının, diğer geliştiricilerin seslerini duymaya ve dikkate almaya başlamasından sonra epeyce gelişti.</p>
<p><strong>Microsoft</strong>’ un çeşitli takımlarının açtığı anketler sayesinde, geliştiricilerin talepleri dinleniyor, değerlendiriliyor ve kayda değer olanlar planlanıp peyder pey yeni sürümlere ilave ediliyor. Hatta takımların ortaya koyduğu “şu da olsa nasıl olur?” ruh halindeki öğeler de geliştiriciler tarafından oylanıyor ve aynı sürece dahil edilebiliyor.<em>(Bloğumdaki takip ettiklerim listesinde bir kaç survey adresini bulabilirsiniz)</em></p>
<p>Hal böyle olunca çok doğal olarak bir sürü sürüm çıkıyor ve var olanlar çabucak eskiyor. Takip edilmesi zor olan ve özellikle <strong>Enterprise</strong> seviyede ki projelerde “<strong>acaba bu teknolojiyi kullanabilir miyiz?”</strong> gibi soruların doğmasına ve ne yazık ki negatif olarak yanıtlanmasına neden olabilecek bir durum bu. Fakat biz yine de üstümüze düşen görevi yapalım ve gerekli anlatımımızı icra ederek öğrendiklerimizi sizlerle paylaşalım. Öyleyse başlayalım <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_74.png" alt="Smile" /></p>
<p><span style="color: #ff0000;">[Makalede yazılanlar Entity Framework 6 Alpha 2 sürümünü baz almaktadır]</span></p>
<p><strong>Entity Framework</strong> alt yapısının sunduğu önemli yaklaşımlardan birisi de <strong>Code-First</strong> modelidir. Bu modele göre geliştiriciler, önce sınıfları basit <strong>POCO</strong><em>(Plain Old CLR Objects)</em> tipler şeklinde tasarlar. Böylece <strong>Conceptual(Domain) Model</strong> oluşturulur. <strong>POCO</strong> tiplerinin tek başına tasarlanması elbette yeterli değildir. <strong>DbContext</strong> türevli bir sınıfında, model de kullanılması düşünülen <strong>POCO</strong> tiplerine ait koleksiyon bazlı özellikleri içeriyor olması gerekmektedir. Bu noktada <strong>DbSet<T></strong> tipinden yararlanılır. Ayrıca tipler arasın ilişkileri betimleyen <strong>Navigation Property</strong>’ ler de tasarlanır.</p>
<p>Sonrasında ise <strong>Entity Framework</strong> ilgili <strong>Context</strong> tipinden yararlanarak çalışma zamanında gerekli veritabanı üretimini icra eder. Peki hiç şu soru aklınıza geldi mi;</p>
<p><em>Bu üretim işlemi sırasında tablolar hangi kuralar göre oluşur, hangi alan Primary Key kabul edilir, tablolar arasındaki ilişkiler(Relations) nasıl belirlenir vb. </em></p>
<p>İşte bu yazımızda bu soruya biraz daha açıklık getirmeye çalışıyor olacağız.</p>
<p><strong>Code First</strong> yaklaşımında, veritabanı tarafının üretilmesi aşamasında devreye girmekte olan bir takım kurallar bütünü bulunmaktadır. <strong>Convention</strong> olarak adlandırılan bu kurallar bütünü aslında <strong>System.Data.Entity.ModelConfiguration.Conventions</strong> isim alanı altında yer alan bazı tipler yardımıyla ifade edilmektedir. <em>(Entity Framework 5.0 sürümü için ilgili isim alanında(namespace) yer alan tipleri </em><a href="http://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.conventions(v=vs.103).aspx" target="_blank"><em>bu adresten inceleyebilirsiniz</em></a><em>)</em></p>
<p>Burada pek çok <strong>Convention</strong> tipi yer almaktadır. İlk olarak bu basit <strong>Convention</strong> tiplerinden bazılarını kavramsal olarak incelemeye çalışalım. Bu amaçla aşağıdaki basit içeriğe sahip olduğumuz bir örnek üzerinden ilerleyebiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_2.png"><img style="margin: 4px 0px; display: inline;" title="efcon_2" src="/pics/efcon_2_thumb.png" alt="efcon_2" width="313" height="463" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace HowTo_EFCodeFirstConvetions
{
public class AzonBookShop
:DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Category> Categories { get; set; }
static AzonBookShop()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<AzonBookShop>());
}
}
public class Category
{
public Guid ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public decimal ListPrice { get; set; }
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
}
}</pre>
<blockquote>
<p>Örnek üzerinde ilerlerken modeli sıkça değiştireceğimizden, static yapıcı metod<em>(Constructor)</em> içerisinde bir strateji seçerek, Initialize sırasında eğer model de bir değişiklik olmuşsa Drop işlemlerinin uygulanması gerektiği belirtilmiştir.</p>
</blockquote>
<p>Şimdi bu modele göre üretilen veritabanı şemasını şöyle kısaca bir inceleyelim dilerseniz.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_1.png"><img style="margin: 4px 0px; display: inline;" title="efcon_1" src="/pics/efcon_1_thumb.png" alt="efcon_1" width="490" height="442" /></a></p>
<p>Dikkat edileceği üzere <strong>Categories</strong> ve <strong>Books</strong> isimli iki tablo üretilmiştir. Her iki tabloda birer <strong>Primary Key</strong> alan bulunmaktadır. İşte burada <strong>Primary Key Convention</strong> kural kümesi devreye girmektedir. Bu kural setine göre, <strong>Guid</strong> veya <strong>int</strong> tipinden olup adı <strong>ID</strong> veya <strong>[SınıfAdı][Id]</strong> notasyonunda olan özellikler, veritabanı şemasında birer <strong>Identity</strong> alan olarak üretilecek ve hatta <strong>Primary Key</strong> şeklinde işaretleneceklerdir.</p>
<p>Senaryomuza <strong>Class</strong> seviyesinde baktığımızda, bir kategorinin altında birden fazla kitabın yer alabileceği görülmektedir. Nesneler arası kurulan bu <strong>ilişkiyi<em>(association)</em></strong> tanımlamak adına <strong>Category</strong> sınıfı içerisinde <strong>ICollection<Book></strong> tipinden bir özellik kullanılmıştır. Bunun karşılığı olarak veritabanı şemasında görüldüğü üzere iki tablo arasında bir <strong>relation</strong> kurulmuştur. Bu ilişki, <strong>Categories</strong> tablosundan <strong>Books</strong> tablosuna doğru <strong>one-to-many</strong> olacak şekildedir. Burada ise <strong>Relation</strong> <strong>Convention</strong> kuralları devreye girmektedir.</p>
<p>Örnekte kasıtlı olarak <strong>Book</strong> sınıfı içerisidne <strong>CategoryId</strong> isimli ayrı bir özellik daha tanımlanmıştır. Mantıksal olarak bu özellik bir kitabın bağlı olduğu <strong>Category</strong> tipini işaret etmek üzere planlanmıştır. Ancak <strong>Relation</strong> <strong>Convention</strong> kurallarına göre isimlendirme de bir sorun vardır. <strong>Category</strong> tablosunun <strong>Identity</strong> şeklindeki <strong>Primary Key</strong> alanı <strong>ID</strong> olarak belirlenmiştir. Bu sebepten tablo şemasına bakıldığında <strong>Category_ID</strong> isimli bir alanın daha eklendiği ve iki tablo arasındaki bire çok ilişkinin, bu alan üzerinden sağlandığı görülmektedir.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_3.png"><img style="margin: 4px 0px; display: inline;" title="efcon_3" src="/pics/efcon_3_thumb.png" alt="efcon_3" width="570" height="490" /></a></p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">USE [AzonBookShop]
GO
ALTER TABLE [dbo].[Books] WITH CHECK ADD CONSTRAINT [FK_dbo.Books_dbo.Categories_Category_ID] FOREIGN KEY([Category_ID])
REFERENCES [dbo].[Categories] ([ID])
GO
ALTER TABLE [dbo].[Books] CHECK CONSTRAINT [FK_dbo.Books_dbo.Categories_Category_ID]
GO</pre>
<p>Relation ile ilişkili SQL script’ i içinde de görüldüğü üzere <strong>Books</strong> tablosunda yer alan <strong>Category_ID</strong> <strong>ForeignKey</strong> olarak belirlenmiş ve <strong>Books</strong> tablosundaki <strong>ID</strong> alanına bağlanmıştır.</p>
<p>Şimdi dilerseniz örneğimizi biraz daha genişletelim ve Context için aşağıdaki sınıf çizelgesinde yer alan <strong>POCO</strong> tipini eklediğimizi düşünelim.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_5.png"><img style="margin: 4px 0px; display: inline;" title="efcon_5" src="/pics/efcon_5_thumb.png" alt="efcon_5" width="488" height="486" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public decimal ListPrice { get; set; }
public virtual Category Category { get; set; }
//public int CategoryId { get; set; }
public Detail BookDetail { get; set; }
}
public class Detail
{
public int PageSize { get; set; }
public double Weight { get; set; }
public bool HardCover { get; set; }
public string Language { get; set; }
}</pre>
<p><strong>Book</strong> sınıfına <strong>Detail</strong> tipinden <strong>BookDetail</strong> isimli bir özellik eklenmiştir. <strong>Detail</strong> sınıfının dikkat çekici özelliği ise <strong>Primary</strong> <strong>Key</strong> <strong>Convention</strong> kuralına uygun bir özellik içermiyor olmasıdır. Bu durumuda <strong>Complex Type Convention</strong> kural seti devreye girecektir ve veritabanı tarafında aşağıdaki sonuçların oluşmasına neden olacaktır.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_4.png"><img style="margin: 4px 0px; display: inline;" title="efcon_4" src="/pics/efcon_4_thumb.png" alt="efcon_4" width="371" height="453" /></a></p>
<p>Görüldüğü üzere <strong>Detail</strong> sınıfının özellikleri, <strong>Book</strong> tablosu içerisinde birer alan<em>(Field)</em> haline getirilmiştir.</p>
<p>Örnekte, makinede yüklü olan <strong>SQL 2008</strong> sunucusu kullanılmıştır. Bu nedenle <strong>app.config</strong> dosyası içerisinde aşağıdakine benzer bir <strong>ConnectionString</strong> bilgisi yer almaktadır. <strong>name</strong> niteliğinin değerinin <strong>Context</strong> sınıf adı ile aynı olması önemlidir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><connectionStrings>
<add name="AzonBookShop" connectionString="data source=.;database=AzonBookShop;integrated security=SSPI" providerName="System.Data.SqlClient"/>
</connectionStrings></pre>
<p>Bu noktada aslında <strong>Connection String Convention</strong> kural seti devreye girmektedir. Bu kural setinin veritabanı şemasını oluştururken baktığı yerlerden birisi, <strong>config</strong> dosyasındaki <strong>connectionStrings</strong> elementi içeriğidir. Eğer <strong>name</strong> özelliğinin değeri ile <strong>DbContext</strong> türevli <strong>Context</strong> tipinin adı eşlenirse, o elemente ait <strong>Connection</strong> bilgisi kullanılaraktan bir şema üretimi gerçekleştirilecektir.</p>
<h2><strong>Ötesi</strong></h2>
<p>Buraya kadar anlatmaya çalıştığımız <strong>Convention</strong> kural setlerinin daha pek çok özelliği bulunmaktadır.<strong> Data Annotations</strong> ve <strong>Fluent API</strong> kullanımı gibi durumlarda ilgili <strong>Convention</strong> kural kümelerinin ezilmesi vb mümkündür. İstenirse bir <strong>Convention</strong> kural seti devre dışı bırakılabilir de. Bunun için <strong>DbContext</strong> üzerinden gelen <strong>OnModelCreating</strong> metodunun ezilmesi ve içerisinde aşağıdakine benzer bir kodun kullanılması yeterlidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace HowTo_EFCodeFirstConvetions
{
public class AzonBookShop
:DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Category> Categories { get; set; }
static AzonBookShop()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<AzonBookShop>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
...</pre>
<p><strong>modelBuilder</strong> tipi üzerinden <strong>Conventions</strong> özelliği ile <strong>Remove</strong> metoduna erişilmekte ve <strong>PluralizingTableNameConvention</strong> sınıfı generic parametre olarak belirtilmektedir. Örneğimizin önceki kısımlarında veritabanı tarafında üretilen tablo adları mutlaka dikkatinizi çekmiştir. Çoğul isimlendirme kuralına göre üretilmişlerdir. Ancak <strong>OnModelCreating</strong> içerisinden bu kural setini kaldırmamız, tablo adlarının sınıf adları olarak tanımlanmasını sağlamaktadır.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_6.png"><img style="margin: 4px 0px; display: inline;" title="efcon_6" src="/pics/efcon_6_thumb.png" alt="efcon_6" width="178" height="232" /></a></p>
<p>Peki, <strong>Convention</strong> kuralları ile ilişkili olarak daha başka neler yapabiliriz? Özellikle bunları manuel olarak ele abilir miyiz? Var olan Convention kurallarını geçersiz kılarak kendi istediğimiz ayarların devreye girmesini nasıl sağlarız?</p>
<p><strong>Convetion</strong> kurallarını manuel olarak ele almanın bir kaç yolu bulunmaktadır. Bunlardan birisi <strong>Lightweight</strong> modelidir. Bu modelde önce bir filtreleme işlemi yapılır ve işlem sonucuna göre konfigurasyonun değiştirilerek uygulanması sağlanır. Örneğin modelimizde yer alan <strong>Category</strong> sınıfının içeriğini aşağıdaki gibi değiştirdiğimiz düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class Category
{
public Guid Signature { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}</pre>
<p>Burada <strong>ID</strong> isimli özelliğin <strong>Signature</strong> olarak değiştirildiği görülmektedir. Bu değişiklik nedeniyle pek tabi <strong>Primary</strong> <strong>Key</strong> <strong>Convention</strong> kural seti <span style="text-decoration: underline;">uygulanamayacaktır</span>. Daha da kötüsü, senaryomuz gereği <strong>Category</strong> ile <strong>Book</strong> arasında bir <strong>relation</strong> tesis edilebilmesi için gerekli <strong>Foreign</strong> <strong>Key</strong> bulunamayacak ve çalışma zamanında aşağıdaki <strong>Exception</strong> ile karşılaşılacaktır.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_7.png"><img style="margin: 4px 0px; display: inline;" title="efcon_7" src="/pics/efcon_7_thumb.png" alt="efcon_7" width="582" height="317" /></a></p>
<p>İşte bu noktada <strong>Lightweight</strong> <strong>Convention</strong> tekniği ile durum çözümlenebilir. Bunun için yine <strong>OnModelCreating</strong> içerisinde bazı işlemler yapılması gerekmektedir. Aynen aşağıda görüldüğü gibi.</p>
<blockquote>
<p>Söz konusu kodda yer alan Properties özelliği EF 6.0 Alpha 2 sürümünde duyurulmuştur. Dolayısıyla bundan sonraki kodlar için güncel PreRelease sürümünü kurarak devam etmelisiniz. Kurulum için <a href="http://msdn.microsoft.com/en-us/data/ee712906" target="_blank">şuradaki adresten</a> yararlanabilirsiniz</p>
</blockquote>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder
.Properties()
.Where(p => p.Name.Contains("Signature"))
.Configure(p => p.IsKey());
}</pre>
<p><strong>modelBuilder</strong> üzerinden <strong>Properties</strong> metodu kullanılarak, <strong>Entity</strong>' deki özellikler arasında <strong>Signature</strong> kelimesini içeren bir tane olup olmadığına bakılmakta ve eğer var ise <strong>IsKey</strong> metodu çağrısı ile bunun bir <strong>Identity</strong> alan olması gerektiği<em>(bir başka deyişle Primary Key Convention kurallarının uygulanması gerektiği)</em> vurgulanmaktadır. Buna göre çalışma zamanı sonucunda veritabanı tarafında aşağıdaki şemanın üretildiği gözlemlenecektir.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_8.png"><img style="margin: 4px 0px; display: inline;" title="efcon_8" src="/pics/efcon_8_thumb.png" alt="efcon_8" width="401" height="457" /></a></p>
<p><strong>Convention</strong> çeşitlerinden bir diğeri de <strong>Model-Based</strong> olan versiyondur. Bu teknikte doğrudan model ile çalışma ve ayarlama şansına sahip oluruz. İlgili tekniği uygulayabilmek için <strong>IEdmConvention</strong>, <strong>IDbConvention</strong> ve <strong>IDbMappingConvention</strong> <strong>arayüzlerini<em>(interface)</em></strong> implemente eden sınıflardan yararlanırız. Aşağıdaki basit örneği göz önüne alalım.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_10.png"><img style="margin: 4px 0px; display: inline;" title="efcon_10" src="/pics/efcon_10_thumb.png" alt="efcon_10" width="402" height="135" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace HowTo_EFCodeFirstConvetions
{
public class StringLengthConversion
: IEdmConvention<EdmProperty>
{
public void Apply(EdmProperty edmDataModelItem, EdmModel model)
{
if (edmDataModelItem.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String)
edmDataModelItem.MaxLength = 200;
}
}
}</pre>
<p><strong>IEdmConvention<EdmProperty></strong> <strong>interface'</strong> ini implemente eden <strong>StringLengthConversion</strong> sınıfı <strong>Apply</strong> metodunu uygulamaktadır. Bu metodun içerisinde, <strong>edmDataModelItem</strong> isimli değişkenin <strong>String</strong> tipi olup olmadığına bakılmakta ve eğer öyleyse <strong>Max</strong> <strong>Length</strong> değeri <strong>200</strong> karakter ile sınırlandırılmaktadır.</p>
<p>Bu işlem pek tabi <strong>Model</strong> içerisinde yer alan ne kadar <strong>String</strong> tipte öğe var ise geçerli olacktır. Tabi söz konusu sınıfın devreye girebilmesi için yine <strong>OnModelCreating</strong> içerisine müdahale edilmelidir. Aşağıdaki kod parçasında görüldüğü gibi.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder
.Properties()
.Where(p => p.Name.Contains("Signature"))
.Configure(p => p.IsKey());
modelBuilder
.Conventions
.AddBefore<StringLengthAttributeConvention>(new StringLengthConversion());
}</pre>
<p>Bu işlem sonucunda veritabanı şemasında aşağıdaki sonuçların oluştuğu gözlemlenecektir.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_9.png"><img style="margin: 4px 0px; display: inline;" title="efcon_9" src="/pics/efcon_9_thumb.png" alt="efcon_9" width="356" height="447" /></a></p>
<p>Bir başka <strong>Convention</strong> modeli ise <strong>Configuration</strong> tabanlı olanıdır. Bu teknikte <strong>IConfigurationConvention</strong> <strong>arayüzünün(intercace)</strong> implenente edildiği bir sınıfın devreye girerek <strong>Convention</strong> kurallarına müdahale etmesi söz konusudur. Aşağıdaki örnek sınıfı göz önüne alalım.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_11.png"><img style="margin: 4px 0px; display: inline;" title="efcon_11" src="/pics/efcon_11_thumb.png" alt="efcon_11" width="518" height="132" /></a></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Reflection;
namespace HowTo_EFCodeFirstConvetions
{
public class StringColumnTypeConvention
:IConfigurationConvention<PropertyInfo,StringPropertyConfiguration>
{
public void Apply(PropertyInfo memberInfo
, Func<StringPropertyConfiguration> configuration)
{
if (configuration().ColumnType == null)
{
configuration().ColumnType = "nvarchar";
configuration().IsNullable = false;
configuration().MaxLength = 50;
}
}
}
}</pre>
<p>Öncelikli olarak ColumnType özelliğinin değerinin null olup olmadığın bakılmaktadır. Bu işlem, ilgili alanın daha önceden oluşturulup oluşturulmadığını da işaret etmektedir. Pek tabi <strong>Convention</strong> kurallarının devreye girebilmesi için yine <strong>OnModelCreating</strong> metoduna bir müdahale de bulunmak gerekmektedir. Aşağıdaki kod parçasında bu durumu görebilirsiniz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder
.Properties()
.Where(p => p.Name.Contains("Signature"))
.Configure(p => p.IsKey());
modelBuilder.Conventions.Add<StringColumnTypeConvention>();
}</pre>
<p>Bu işlem sonrasında veri tabanı şemasının aşağıdaki gibi üretildiği görülecektir.</p>
<p><a href="https://buraksenyurt.com/pics/efcon_12.png"><img style="margin: 4px 0px; display: inline;" title="efcon_12" src="/pics/efcon_12_thumb.png" alt="efcon_12" width="357" height="377" /></a></p>
<p>Dikkat edileceği üzere <strong>String</strong> özelliklerin karşılığı olarak <strong>nvarchar</strong> tipinde olan, <strong>null</strong> değer içeremeyen ve <strong>maksimum 50</strong> karakter uzunluğunda içerik tutabilen alanlar üretilmiştir.</p>
<p><strong>Code First</strong> yaklaşımında <strong>Convention</strong> kullanımı ile ilişkili olarak daha ileri seviye uygulamalar da mevcuttur. Söz gelimi Custom Attribute’ lar le yeni Convention kural setleri tanımlanabilir. Özellikle LightWeight modelinde kullanılabilecek epey fazla fonksiyonllik bulunmaktadır. Bu konuda <a href="http://msdn.microsoft.com/en-us/data/jj819164" target="_blank">şu adresteki</a> yazının son kısımlarını da değerlendirebilir ve kendi denemelerinizi yaparak konuyu irdelemeye çalışabilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_162.png" alt="Winking smile" /></p>
<p><span style="color: #ff0000;">[Makalede yazılanlar Entity Framework 6 Alpha 2 sürümünü baz almaktadır]</span></p>
<p><a href="https://buraksenyurt.com/pics/2012%2f12%2fHowTo_EFCodeFirstConvetions.zip">HowTo_EFCodeFirstConvetions.zip (2,14 mb)</a><em> (Dosya boyutunun büyümemesi için Packages klasörü çıkartılmıştır. EF' in 6ncı sürümünü projeye indirmeniz gerekebilir)</em></p>2012-12-12T05:15:00+00:00entity frameworkentity framework 6.0dbcontentconventionfluent apidbsetnavigationpropertyprimary key conventionrelation conventioncomplex type conventionconnection string conventionlightweight convention modelmodel based conventionconfiguration based coventioniedmconventionidbconventionidbmappingconventionbsenyurtEntity Framework alt yapısının sunduğu önemli yaklaşımlardan birisi de Code-First modelidir. Bu modele göre geliştiriciler, önce sınıfları basit POCO(Plain Old CLR Objects) tipler şeklinde tasarlar. Böylece Conceptual(Domain) Model oluşturulur. POCO tiplerinin tek başına tasarlanması elbette yeterli değildir. DbContext türevli bir sınıfında, model de kullanılması düşünülen POCO tiplerine ait koleksiyon bazlı özellikleri içeriyor olması gerekmektedir. Bu noktada DbSet<T> tipinden yararlanılır. Ayrıca tipler arasın ilişkileri betimleyen Navigation Property’ ler de tasarlanır.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=0d3846ef-68c3-4cae-89f5-ed6b8c4e8d326https://buraksenyurt.com/trackback.axd?id=0d3846ef-68c3-4cae-89f5-ed6b8c4e8d32https://buraksenyurt.com/post/Entity-Framework-6-Code-First-Convention#commenthttps://buraksenyurt.com/syndication.axd?post=0d3846ef-68c3-4cae-89f5-ed6b8c4e8d32