Makale Özeti

Bu yazımda entity framework üzerinde code-first yaklaşımını gerçek bir senaryo ile birleştirerek çoklu uygulamalarda context ve respository sınıflarının tasarımı ve kodlaması üzerine duracağım.

Makale

Herkese merhaba,

Bu yazımda entity framework üzerinde code-first yaklaşımını gerçek bir senaryo ile birleştirerek çoklu uygulamalarda context ve respository sınıflarının tasarımı ve kodlaması üzerine duracağım.

Yazılım geliştiriciler olarak yaptığımız uygulamaların , genişlemeye açık ve bunun kolay bir şekilde sağlanabiliyor olmasını isteriz, tabi ki bu düşüncenin projenin kodlama safhasına başlamadan önce yazılımın tasarımı üzerinde kafa yorma gerekliliğinide beraberinde getirir.Entity framwork yapısında gerçekleştirdiğimiz uygulamalarda ise veritabanı ile iletişimin sağlandığı Context tipleri ve bu iletişimde business operasyonlarımızı yaptığımız repository sınıfları üzerinde durmak gerekir.

Bu yazımda senaryo üzerinden giderek context ve repository sınıflarının birbiriyle olan ilişkilerinin nasıl  olabileceğini görmeye çalışacağız.

Senaryomuz; bir topluluk sitesi yapacağınız düşünün.Bu sitede bir blog,forum,url kısaltma servisi,sso ve daha bir çok birbiriyle ilişkili ya da değil uygulamanın  olabileceği açıktır.Siz başlangıç olarak bir yazıların yazıldığı ve paylaşıldığı bir uygulamada yapsanız dahi sonraki aşamada bir forum altyapısı ya da daha bir çok uygulama ekleme ihtimaliniz olabilecektir.Böyle bir senaryo üzerinde Entity Framework altyapısı kullanarak nasıl bir context tasarlamamız gerektiğini düşünelim.

 Birden fazla uygulama tek çözüm

Yapacağımız uygulamalar kaçınılmaz olarak katmanlı yapıda tasarlanır.Bir çok uygulamada kullanılan email,loglama,validasyon,caching ve benzeri bir çok işlemi yapan yardımıcı tipler her uygulama için yazılması pek doğru bir yaklaşım olmaz bu nedenle çözümlerimiz için Core bir kütüphane yazarak bu gibi işlemelerin bir kere yazılıp n kez kullanılmasını sağlamış oluruz.

Veri tabanı seviyesinde tasarım; birden fazla uygulama tek bir veritabanında olabileceği gibi uygulamalar için ayrı ayrı veritabanı tasarlanabilir.Eğer tek bir veritabanı üzerinden çalışılmak isteniyorsa uygulamlara göre tabloları ayırmak için Schemalar kullanılabilir.

Context tasarımı; Context sınıfları veritabanı sayısına göre tasarlanmazlar.Her uygulamaya ait bir context sınıfı yapılabileceği gibi tek bir uygulamaya ait birden fazla Context sınıfıda yazılabilir.Eğer bir tek uygulama için birden fazla Context yazılıyorsa bu proje DDD(Domain Driven Development) alan yönelimli olarak geliştirilmiştir. Bir banka çözümünü düşürsek;

Dikkat edersek biz burada senaryomuz gereği Context sınıfımızı uygulama temelli geliştirdiğimizi söyleyebiliriz.Fakat bunu kontrollü bir şekilde gerçekleşmesi gerekiyor.

Biraz kod yazalım

Context sınıflarını tasarlarken ben factory method desenini kullanacağım.Bu desen ile her context sınıfına ait bir de factory sınıfı yazılır ve bu sınıf sadece bu context in üretiminden sorumludur.Eğer çok fazla context e sahip olmayacağınızı düşünüyorsanız factory desenini de kullanabilirsiniz, tek farkla burada bir sürü fabrika sınıfına sahip olma yerine bunlar için enum tanımlarak tek bir method da üretimi sağlayabilirsiniz.

 

class BlogContext
        :DbContext
    {
        IDbSet<Post> Posts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            Database.SetInitializer<BlogContext>(new DropCreateDatabaseIfModelChanges<BlogContext>());
            base.OnModelCreating(modelBuilder);
        }
    }
class SingleSignOnContext
        :DbContext
    {
        IDbSet<User> Users { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            Database.SetInitializer<SingleSignOnContext>(new DropCreateDatabaseIfModelChanges<SingleSignOnContext>());
            base.OnModelCreating(modelBuilder);
        }

    }
class ForumContext
        :DbContext
    {
        IDbSet<Question> Questions { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            Database.SetInitializer<ForumContext>(new DropCreateDatabaseIfModelChanges<ForumContext>());
            base.OnModelCreating(modelBuilder);
        }
    }
class ForumContextFactory
        :ContextFactory
    {
        public override System.Data.Entity.DbContext GetContext()
        {
            return new ForumContext();
        }
    }
class BlogContextFactory
        :ContextFactory
    {
        public override System.Data.Entity.DbContext GetContext()
        {
            return new BlogContext();
        }
    }
  class SingleSignOnContextFactory
        :ContextFactory
    {
        public override System.Data.Entity.DbContext GetContext()
        {
            return new SingleSignOnContext();
        }
    }


Yazılan kodların class-diagram görüntülerine bakalım.

Buradaki en önemli sınıf ContextFactory sınıfıdır. Tablolara ait Repository sınıflarımız doğrudan bu ContextFactory sınıfını kullanarak ilgili Contextleri kontrollü bir şekilde üretilmesini sağlamış olur.

Repository sınıfları; bu sınıflar Repository Design pattern e uygun şeklide tasarlanır.Martin Fowler a göre bu tasarım desenini enterprise uygulama mimari deseni içerisinde yer alır.Temelde amacı uygulamayı veri yapısından soyutlamakdır.

Şimdi Repository sınıflarımız tasarlayalım.Bu sınıflar ilgili modele ait işlemlerin gerçekleştiği operasyon sınıfları diyebiliriz.Temelde her model için CRUD işlemeleri ve sayfalama ait standart işlemleri yapıyoruz.Bu yüzden kod tekrarını azaltmak ve kendi custom operasyonlarımıza odaklanmak adına generic bir RepositoryBase sınıfı yazalım.

İlk olarak bu sınıfın uygulayacağı işlemler için bir arayüz (Interface) yazalım.

 

public interface IRepository<TModel>
{
    IQueryable<TModel> All();
    IQueryable<TModel> Filter(Expression<Func<TModel, bool>> predicate);
    IQueryable<TModel> Filter<Key>(Expression<Func<TModel, bool>> filter,
            out int total, int index = 0, int size = 50);
    bool Contains(Expression<Func<TModel, bool>> predicate);
    TModel Find(params object[] keys);
    TModel Find(Expression<Func<TModel, bool>> predicate);
    TModel Create(TModel T);
    int Delete(TModel T);
    int Delete(Expression<Func<TModel, bool>> predicate);
    int Update(TModel T);
}


 Şimdi bu arayüzü uygulayacak olan RepositoryBase yazmalıyız ve her model için ayrı ayrı yazılan repository sınıfları repositorybase sınıfından kalıtılarak yapılacak standart işlemleri sahip olmuş oluruz.Bu sınıfta yapılan insert delete,update gibi işlemler virtual olarak tanımlanırsa override edilmesine izin verilmiş olur ve gerektiğinde istenilen method ezilip kendi custom işlemlerimizi yazabiliriz.

 

public class RepositoryBase<TModel>
    : IRepository<TModel>
    where TModel : class
{ 
    #region initliazer

    private readonly IDbSet<TModel> dbset;
    private DbContext concreteContext;

    protected ContextFactory ContextFactory
    {
        get;
        private set;
    }

    protected DbContext DataContext
    {
        get { return concreteContext ?? (concreteContext = ContextFactory.GetContext()); }
    }

    public RepositoryBase(ContextFactory cfactory)
    {
        ContextFactory = cfactory;
        dbset = DataContext.Set<TModel>();
    }

    #endregion
        
    public virtual IQueryable<TModel> All()
    {
        return dbset.AsQueryable();
    }

    public virtual IQueryable<TModel> Filter(Expression<Func<TModel, bool>> predicate)
    {
        return dbset.Where(predicate).AsQueryable<TModel>();
    }

    public virtual IQueryable<TModel> Filter<Key>(Expression<Func<TModel, bool>> filter, out int total, int index = 0, int size = 50)
    {
        int skipCount = index * size;
        var resetSet = filter != null ? dbset.Where(filter).AsQueryable() : dbset.AsQueryable();
        resetSet = skipCount == 0 ? resetSet.Take(size) : resetSet.Skip(skipCount).Take(size);

        total = resetSet.Count();

        return resetSet.AsQueryable();
    }

    public virtual bool Contains(Expression<Func<TModel, bool>> predicate)
    {
        return dbset.Count(predicate) > 0;
    }

    public virtual TModel Find(params object[] keys)
    {
        return dbset.Find(keys);
    }

    public virtual TModel Find(Expression<Func<TModel, bool>> predicate)
    {
        return dbset.FirstOrDefault(predicate);
    }

    public virtual TModel Create(TModel t)
    {
        var newEntry = dbset.Add(t);
        concreteContext.SaveChanges();

        return newEntry;
    }

    public virtual int Delete(TModel t)
    {
        dbset.Remove(t);
        return concreteContext.SaveChanges();
    }

    public virtual int Delete(Expression<Func<TModel, bool>> predicate)
    {
        var objects = Filter(predicate);

        foreach (var obj in objects)
            dbset.Remove(obj);
        return concreteContext.SaveChanges();
    }

    public virtual int Update(TModel t)
    {
        var entry = concreteContext.Entry(t);
        dbset.Attach(t);
        entry.State = EntityState.Modified;
        return 0;
    }

    public void Dispose()
    {
        if (this.concreteContext != null)
            this.concreteContext.Dispose();
    }
}


Nihayi olarak repository sınıflarının class-diagram görünümünü paylaşmak istiyorum. 

Sonuç olarak;

Entity Framework Codefirst ile Factory Method kullanarak Context tiplerinin üretimi sağlıyoruz.Repository sınıfları generic bir RepositoryBase sınıfı ile çok daha soyut ve basit repository sınıfları yazmış olduk.Birden fazla uygulama için EF Codefirst yaklaşımının nasıl kullanılabileceği görmüş olduk.

Kodları incelemek için https://github.com/yemrekeskin/EFCodefirst.MultiApplication  adresine girebilirisniz