Yükleniyor...

11 Temmuz 2019

Lazy Loading için Repository Örneği

Merhabalar herkese. Kısa bir aradan sonra tekrar buradayım. Asp.Net projelerinde Lazy Loading kullanımının sakıncalı bir durum olduğundan bir yazımda söz etmiştim. Lazy Loading kullanmadan web sayfalarımızın hızını ciddi oranda arttırabiliriz. Peki gelelim katmanlı mimari yapısına… Hemen hemen herkes Asp.net projelerinde repository pattern’i kullanır. Generic metotlar sayesinde işlemlerimizi kolay bir şekilde gerçekleştirebiliriz. Lazy Loading içinde uygun metotlar yazabiliriz. Linq extension metotlarından Include metodu tamda bizim ihtiyacımızı görüyor. Şimdi gelelim basit ama kullanışlı bir repository pattern örneğini inceleyelim. Tabi tüm bu mekanizma için katmanlı mimari kullanımı çok önemli.

Öncelikle katmanlarımızı oluşturalım. Katmanlarımız ufak bir proje için şu şekilde olmalıdır. Örneğin projemizin ismi SeizeTheDay olsun.

  • SeizeTheDay.Business
  • SeizeTheDay.Common
  • SeizeTheDay.DataAccess
  • SeizeTheDay.Entities
  • SeizeTheDay.WebUI

Takdir edersiniz ki veri tabanından gelen entity sınıflarımız .Entities adlı katmanımızda yer almaktadır. Repository Pattern’in bulunduğu katman ise DataAccess olacaktır. Şimdi adımlarımıza başlayalım.

1.Adım Klasörleri Oluşturmak

Bahsettiğim gibi repository pattern .DataAccess katmanımızda yer alacak. Ben bu yapıyı SeizeTheDay adlı projemde kullandım. Fakat veri tabanı olarak MySQL kullanmıştım. EntityFramework ile hemen hemen aynı yapı zaten.

DataAccess katmanımız için klasörleme şu şekildedir. Bahsettiğim gibi bu projede MySQL kullandığımdan dolayı MySQL adlı bir klasör oluşturma gereksinimi duydum. Siz Mssql kullanıyorsanız Interface ve classlarınızı EntityFramework adlı klasörde barındırmalısınız.

Örneğin Abstract adlı klasörün içi şu şekildedir. Abstract adlı klasörümüzün içinde aslında bir nevi soyut adını verdiğimiz interface sınıflarımız yer alacaktır. Bu sınıfların içerilerinde imza adını verdiğimiz metotlar yer alacaktır. Dikkat edilmesi gereken husus şudur, veri tabanında ki her bir entity’e yani tabloya denk gelecek bu interfacelerimizi oluşturmamızdır. Bu interface’lerin içinde birde IEntityRepository adını verdiğimiz bizim için önemli bir sınıfımız bulunmakta. Repository’e ait tüm imzalarımız burada bulunmakta. İşte Lazy Loading içinde birbirinden önemli imzalarımızı burada barındırıp gerekli Conrete sınıfından bu interface’i implement edeceğiz. Şimdi gelelim IEntityRepository adlı interface’imizi incelemeye.

2.Adım Interface’leri Oluşturmak

IEntityRepository interface’imiz şu şekildedir.

using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Objects.DataClasses;
using System.Linq;
using System.Linq.Expressions;

namespace SeizeTheDay.DataAccess.Abstract.MySQL
{
    public interface IEntityRepository<T> where T: EntityObject, new()  //EntityObject is used for MySQL
    {
        List<T> GetList();
        void Add(T entity);
        void Delete(T entity);
        void Update(T entity);
        void Update2(T entity);
        void Add3(T entity);
        int Add2(T entity);
        IQueryable<T> GetAll();

        T FirstOrDefault();


        List<T> Query(Expression<Func<T, bool>> where);  //According to query
        List<T> GetAllLazyLoad(Expression<Func<T, bool>> filter, params Expression<Func<T, object>>[] children);
        List<T> TolistInclude(params Expression<Func<T, object>>[] children);

        List<T> StringInclude(params string[] children); //string include toList
        List<T> StringIncludeWithExpression(Expression<Func<T, bool>> filter, params string[] children); //string include toList with exp.

        T StringIncludeSingle(params string[] children); //string include single
        T StringIncludeSingleWithExpression(Expression<Func<T, bool>> filter, params string[] children); //string include single with exp.

        T GetFirstOrDefaultInclude(Expression<Func<T, bool>> filter, params Expression<Func<T, object>>[] children);
        T GetFirstOrDefaultInclude2(Expression<Func<T, bool>> filter, params Expression<Func<T, object>>[] children);
        List<T> Include(string includeTable);
        T Find(Expression<Func<T, bool>> filter);
    }
}

Burada dikkat edilmesi gereken husus EntityObject adlı nesnenin MySQL için kullanılması. Neden bunu kullanmam gerektiğine dair bilgi vermem gerekirse, MySQL’i projede kullanabilmek için Devart adlı firmanın dotConnect for MySQL adlı ürününü kullanıyorum. Veri tabanındaki her bir entity’i bir sınıf olarak değil bir EntityObject olarak implement ediyor, bu nedenle her bir entity aynı zamanda Devart’ın ürünü için bir EntityObject anlamına geliyor.

Sizin yapmanız gereken ise T adlı nesnenin bir Class olduğunu belirtmek. EntityFramework’de veri tabanındaki her bir Entity aslı aynı zamanda bir Class’dır.

Repository’imizin interface’i hazır, şimdi gelelim diğer interface’ler için örneğimizi inceleyelim.

using Xgteamc1XgTeamModel;

namespace SeizeTheDay.DataAccess.Abstract.MySQL
{
    public interface IForumDal: IEntityRepository<Forum>
    {

    }
}

Diğer interface’lerimiz de bu şekildedir. IForumDal’ı IEntityRepository’den implement ediyoruz ve parametre olarak bir Entity nesnesi gönderiyoruz. Mantık tamamen bu. Şimdi gelelim concrete sınıflarımızı oluşturalım.

3.Adım Concrete Sınıflarını Oluşturmak

Concrete klasörümüzün içerisi ise bu şekildedir. Adlandırmayı MySQL’e göre yaptığımı belirtmek isterim. Eğer siz Mssql kullanıyorsanız tüm bu sınıflar EntityFramework’un içerisinde olup, adlandırma ise Ef diye başlamalıdır. Örneğin, EfEntityRepositoryBase şeklinde. Şimdi gelin, Repository sııfımızı inceleyelim. En önemli adım burası 🙂

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Core.Objects.DataClasses;
using System.Linq;
using System.Linq.Expressions;
using SeizeTheDay.DataAccess.Abstract.MySQL;

namespace SeizeTheDay.DataAccess.Concrete.MySQL
{
    public class MyEntityRepositoryBase<TEntity, TContext> : IEntityRepository<TEntity>
        where TEntity : EntityObject, new()  //EntityObject is used for MySQL
        where TContext : ObjectContext, new()  //ObjectContext is used for MySQL
    {

        public List<TEntity> GetList()
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().ToList();
            }
        }

        public void Add(TEntity entity)
        {
            using (TContext context = new TContext())
            {
                context.CreateObjectSet<TEntity>().AddObject(entity);
                context.SaveChanges();
            }
        }

        public void Delete(TEntity entity)
        {
            using (TContext context = new TContext())
            {
                context.Attach(entity);
                context.CreateObjectSet<TEntity>().DeleteObject(entity);
                context.SaveChanges();
            }
        }

        public void Update(TEntity entity)
        {
            using (TContext context = new TContext())
            {
                context.Attach(entity);
                context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                context.SaveChangesAsync();
               
            }
        }
 
        public List<TEntity> Query(Expression<Func<TEntity, bool>> where = null)
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().Where(where).ToList();
            }
        }

        public List<TEntity> Include(string includeTable)
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().Include(includeTable).ToList();
            }
        }

        public TEntity Find(Expression<Func<TEntity, bool>> filter)
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().Where(filter).FirstOrDefault();
            }
        }
      
        public List<TEntity> GetAllLazyLoad(Expression<Func<TEntity, bool>> filter, params Expression<Func<TEntity, object>>[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().ToList();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).Where(filter).ToList();              
                return query.ToList();
            }
        }


        public int Add2(TEntity entity)
        {
            using (TContext context = new TContext())
            {
                context.CreateObjectSet<TEntity>().AddObject(entity);
                return context.SaveChanges();
            }

        }

        public TEntity FirstOrDefault()
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().FirstOrDefault();
            }
        }

        public TEntity GetFirstOrDefaultInclude(Expression<Func<TEntity, bool>> filter, params Expression<Func<TEntity, object>>[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().FirstOrDefault();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).Where(filter).FirstOrDefault();
                return query;
            }           
        }

        public  TEntity GetFirstOrDefaultInclude2(Expression<Func<TEntity, bool>> filter=null, params Expression<Func<TEntity, object>>[] includes)
        {
            using (TContext context = new TContext())
            {
                IQueryable<TEntity> query = context.CreateObjectSet<TEntity>();
                foreach (Expression<Func<TEntity, object>> include in includes)
                    query = query.Include(include);

                return query.FirstOrDefault(filter);
            }
        }

        public List<TEntity> TolistInclude(params Expression<Func<TEntity, object>>[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().ToList();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).ToList();
                return query.ToList();
            }
        }

        public void Add3(TEntity entity)
        {
            TContext context = new TContext();
            context.CreateObjectSet<TEntity>().AddObject(entity);
            context.SaveChanges();

        }

        public void Update2(TEntity entity)
        { 
             TContext context = new TContext();           
             context.Attach( entity);
             context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
             context.SaveChanges();
            
        }

        public IQueryable<TEntity> GetAll()
        {
            using (TContext context = new TContext())
            {
                return context.CreateObjectSet<TEntity>().AsQueryable<TEntity>();
            }
        }

        public List<TEntity> StringInclude(params string[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().ToList();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).ToList();
                return query.ToList();
            }
        }

        public List<TEntity> StringIncludeWithExpression(Expression<Func<TEntity, bool>> filter, params string[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().ToList();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).Where(filter).ToList();
                return query.ToList();
            }
        }

        public TEntity StringIncludeSingle(params string[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().FirstOrDefault();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).FirstOrDefault();
                return query;
            }
        }

        public TEntity StringIncludeSingleWithExpression(Expression<Func<TEntity, bool>> filter, params string[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().FirstOrDefault();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).Where(filter).FirstOrDefault();
                return query;

            }
        }
    }
}

Burada dikkat edilmesi gereken husus ise şudur, EntityObject ve ObjectContext nesneleri MySQL için yazılmıştır. Sırasıyla siz EntityFramework için Class ve DbContext yazmanız gerekmektedir.

 public List<TEntity> StringIncludeWithExpression(Expression<Func<TEntity, bool>> filter, params string[] children)
        {
            using (TContext context = new TContext())
            {
                var query = context.CreateObjectSet<TEntity>().ToList();
                foreach (var child in children)
                    query = context.CreateObjectSet<TEntity>().Include(child).Where(filter).ToList();
                return query.ToList();
            }
        }

Şimdi yukarıdaki kodu inceleyelim. Bu fonksiyon geriye List döndürmektedir. Fonksiyon iki tane parametre alabiliyor, birincisi Linq expression, diğeri ise Lazy Loading için. Yani örnek olarak şöyle; siz Users listesini getirirken örneğin, birde her bir kullanıcıya ait olan Makaleleri getirmek istiyorsanız parametre olarak Makale tablosunun ismini yazmalısınız. Bir detaylı örnek ile bu yapı oturacaktır. Ama önce Business katmanını oluşturmamız ve gerekli Dependency Injection optimizasyonları tamamlamamız gerekmektedir.

4.Adım Business Katmanını Oluşturmak

Business katmanında yine Abstract ve Concrete olmak üzere iki ana klasörümüz olması gerekmektedir. Business katmanında repository’de oluşturduğumuz fonksiyonları, metotları çok kolay bir şekilde kullanacağız. Business katmanında, Service ve Manager olmak üzere 2 ana fonksiyonlarla çalışacağız.

Klasörleme bu şekildedir. Tüm ağır yükleri, hesaplamaları kitaplamayı aslında Business katmanı gerçekleştirir. Siz sadece gerekli controller’dan bu servisleri çağırarak gerekli işlemleri yaparsınız. Örneğin IForumService interface’ini inceleyelim.

using System.Collections.Generic;
using Xgteamc1XgTeamModel;

namespace SeizeTheDay.Business.Abstract.MySQL
{
    public interface IForumService
    {
        List<Forum> GetList();
        void Add(Forum forum);
        void Delete(Forum forum);
        void Update(Forum forum);
        Forum GetByForum(int id);
        List<Forum> GetByForumID(int id);
        List<Forum> GetAllLazy();
    }
}

Servislerimiz bu şekildedir. Her bir servisin imzaları vardır. Şimdi Concrete klasörünün altında Manager sınıflarımızı inceleyelim. Bu Manager sınıfları gerekli kendilerine ait interface’lerden implement edilecektir, yani servislerden.

using System.Collections.Generic;
using SeizeTheDay.Business.Abstract.MySQL;
using SeizeTheDay.DataAccess.Abstract.MySQL;
using Xgteamc1XgTeamModel;

namespace SeizeTheDay.Business.Concrete.MySQL
{
    public class ForumManager : IForumService
    {
        private IForumDal _forumDal;

        public ForumManager(IForumDal forumDal)
        {
            _forumDal = forumDal;
        }
        public void Add(Forum forum)
        {
            _forumDal.Add(forum);
        }

        public void Delete(Forum forum)
        {
            _forumDal.Delete(forum);
        }

        public List<Forum> GetAllLazy()
        {
            return _forumDal.StringInclude("User", "User.UserInfoe_Id", "ForumPosts","ForumPosts.User", "ForumPosts.User.UserInfoe_Id","ForumTopics");
        }

        public Forum GetByForum(int id)
        {
            return _forumDal.Find(x => x.ForumID == id);
        }

        public List<Forum> GetByForumID(int id)
        {
            return _forumDal.Query(x => x.ForumID == id);
        }

        public List<Forum> GetList()
        {
            return _forumDal.GetList();
        }

        public void Update(Forum forum)
        {
            _forumDal.Update(forum);
        }
    }
}

ForumManager sınıfımız bu şekildedir. Bizim için önemli olan GetAllLazy adındaki metodumuzdur. Fakat bundan önce şu konudan bahsetmek isterim.

 private IForumDal _forumDal;

        public ForumManager(IForumDal forumDal)
        {
            _forumDal = forumDal;
        }

Bu kısım aslında çok şey ifade ediyor. Dependency Injection adını verdiğimi prensibi burada gerçekleştiriyoruz. Constructorda IForumDal’ı set ederek, IForumDal sayesinde repository’e erişim hakkını elde ediyorum. Yani _forumDal. yazdığımızda repository’de ki tüm fonksiyonlara erişiyoruz. Şimdi gelelim GetAllLazy adlı fonksiyonu incelemeye.

public List<Forum> GetAllLazy()
        {
            return _forumDal.StringInclude("User", "User.UserInfoe_Id", "ForumPosts","ForumPosts.User", "ForumPosts.User.UserInfoe_Id","ForumTopics");
        }

_forumDal objesini kullanarak repository’e eriştik. Tüm bu emeklerimizden sonra en kritik yer ise, çağırdığımız fonksiyona parametre olarak ne göndereceğimiz. StringInclude fonksiyonu sadece tablo isimler olarak parametre alacak yani herhangi bir expression almayacak. Yukarıdaki repository kodlarımızdan StringInclude fonksiyonunu inceleyebilirsiniz.

Forum adında bir tablom var ve User,ForumPost ve ForumTopic tablolarıyla ilişkili. Forum listesini çağırdığım zaman aslında her bir foruma ait ForumPostları ve ForumTopicleri gelecektir. ForumPosts.User ise Foruma ait ForumPostlarını oluşturan kişileri çekmek için kullanılır.

Yapı biraz karışık gibi gözüksede aslında basit. Gördüğünüz gibi ihtiyacımız olan tabloları çağırıyoruz. Lazy Loading’i kullansaydık eğer ihtiyacımız olmayan verilerde gelecekti. Buda performans açısından çok ama çok ciddi bir zaafiyet anlamına gelmektedir.

Posted in Asp.Net Core MVC, Asp.net MVC, Bilişim, Repository PatternTaggs:

Bir Cevap Yazın