Çalışma Mantığı
Yazılım uygulamaları genellikle yoğun bir şekilde veritabanı işlemleri gerçekleştiren ekosisteme sahip yapılar olduğundan dolayı, ilgili uygulamanın herbir noktasında gerekli veritabanı işlemlerini tekrar tekrar yazmak yerine bu işlemleri tekrar kullanılabilirlik prensibi çerçevesinde daha pratik bir şekilde tek seferde yapmamızı sağlayan bir yapılanma geliştirmemiz gerekecektir. İşte bu yapılanma Repository sınıfı olacaktır.
Repository sınıfı, içerisinde generic yapılanmalarla geliştirilen temel operasyonel veritabanı metotlarını barındıran bir sınıftır. Yukarıdaki diyagramdan da görüldüğü üzere sorgu generate etme ve genellikle ORM araçlarıyla kombine edilerek veri eşleştirme sorumluluğunu üstlenmektedir. Bu yeteneklerini ana business sorumluluğunu yüklenen sınıflara kalıtım yoluyla aktarmakta ve bu şekilde kazandırmaktadır.
Uygulama Yöntemi
Repository Design Pattern’i uygulayabilmek için öncelikle tüm veritabanı işlemlerini operatif olarak temsil edecek interface tanımlamamız gerekmektedir. Aslında bu bir zorakilik değil, gelenektir. Şöyle konuya dair kısa bir araştırma yaparsak, genellikle tasarlanan Repository sınıfının genel hatlarının belirlenebilmesi için içerisinde kullanılacak tüm metotları barındıran bir interface ile imzalandığı gözlenmektedir. Kısaca bu bizim çalışmamızı kolaylaştıracak olan bir hamledir.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | public interface IRepository<T> where T : class { List<T> Get(); List<A> Get<A>() where A : class; List<T> GetWhere(Expression<Func<T, bool>> metot); List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class; T GetSingle(Func<T, bool> metot); A GetSingle<A>(Func<A, bool> metot) where A : class; T GetById(int id); A GetById<A>(int id) where A : class; bool Add(T model); bool Add<A>(A model) where A : class; bool Remove(T model); bool Remove<A>(A model) where A : class; bool Remove(int id); bool Remove<A>(int id) where A : class; bool Update(T model, int id); bool Update<A>(A model, int id) where A : class; int Save(); } |
Yukarıdaki kod bloğuna göz atarsanız eğer IRepository isimli bir interface tanımlanmıştır ve içerisine aklımıza gelen tüm veritabanı işlemlerini yapmamızı sağlayacak olan metot imzaları eklenmiştir. Burada dikkat edilmesi gereken iki husus vardır; birincisi, interface generic olarak tasarlanmıştır… Bunun nedeni, tasarımda kullanılacak tipi süreçte geliştirici belirleyecektir ve tüm bu yapılanma belirtilen o tipe göre geliştirilecektir. İkinci husus ise geliştiricinin belirlediği tipin dışındaki türlerde yapılacak işlemleri karşılayabilmek için T tipinden bağımsız generic olarak tasarlanan metotlar geliştirilmiştir. Burada örnek vermek gerekirse; “Araba” modelini kullandığımız bir sınıf içerisinde “Surucu” modeline ait işlem yapmamız gerekiyorsa eğer işte burada generic metotlar devreye girecektir.
Eee madem araba üzerinden örnek verdik ilgili Repository sınıfımızı oluştururken bir ArabaContext nesnesi üzerinde işlem yaptığını varsayarak devam edelim. Sırada tasarladığımız interface’i aşağıdaki gibi Repository ismini verdiğim generic bir sınıfa uygulayarak concrete halini oluşturuyorum. Bizler örneğimizi Web API uygulamasına uygun bir sınıf ile ele almaktayız.
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | public class Repository<Type> : ControllerBase, IRepository<Type> where Type : class { protected ArabaContext _arabaContext; public Repository(ArabaContext arabaContext) { _arabaContext = arabaContext; } [NonAction] public DbSet<Type> Table() { return Table<Type>(); } [NonAction] public DbSet<A> Table<A>() where A : class { return _arabaContext.Set<A>(); } [NonAction] public bool Add(Type model) { return Add<Type>(model); } [NonAction] public bool Add<A>(A model) where A : class { Table<A>().Add(model); Save(); return true; } [NonAction] public List<Type> Get() { return Get<Type>(); } [NonAction] public List<A> Get<A>() where A : class { return Table<A>().ToList(); } [NonAction] public Type GetById(int id) { return GetById<Type>(id); } [NonAction] public A GetById<A>(int id) where A : class { return GetSingle<A>(t => typeof(A).GetProperty("Id").GetValue(t).ToString() == id.ToString()); } [NonAction] public bool Remove(Type model) { return Remove<Type>(model); } [NonAction] public bool Remove<A>(A model) where A : class { Table<A>().Remove(model); return true; } [NonAction] public bool Remove(int id) { return Remove<Type>(id); } [NonAction] public bool Remove<A>(int id) where A : class { A silinecekData = GetSingle<A>(x => (int)typeof(A).GetProperty("Id").GetValue(x) == id); Remove<A>(silinecekData); Save(); return true; } [NonAction] public int Save() { return _arabaContext.SaveChanges(); } [NonAction] public bool Update(Type model, int id) { return Update<Type>(model, id); } [NonAction] public bool Update<A>(A model, int id) where A : class { A guncellenecekNesne = GetById<A>(id); var tumPropertyler = typeof(A).GetProperties(); foreach (var property in tumPropertyler) if (property.Name != "Id") property.SetValue(guncellenecekNesne, property.GetValue(model)); Save(); return true; } [NonAction] public List<Type> GetWhere(Expression<Func<Type, bool>> metot) { return GetWhere<Type>(metot); } [NonAction] public List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class { return Table<A>().Where(metot).ToList(); } [NonAction] public Type GetSingle(Func<Type, bool> metot) { return GetSingle<Type>(metot); } [NonAction] public A GetSingle<A>(Func<A, bool> metot) where A : class { return Table<A>().FirstOrDefault(metot); } } |
Yukarıdaki kod bloğunu incelerseniz eğer Repository isminde bir sınıf oluşturulmuştur ve biraz önce oluşturduğumuz IRepository isimli interfaceden türetilmiştir. Implementasyon neticesinde oluşturulan operatif metotların içeriğine göz atarsanız eğer isimlerine uygun işlevler gerçekleştirilmekte ve gerektiği zaman reflection dahi kullanılmaktadır ve böylece T yahut A tipine belirtilen türe göre uygun tabloda işlemler gerçekleştirilmektedir.
Peki, T ve A türlerinin ne olduğunu biliyor muyuz?
Hayır bilmiyoruz.
Öyleyse türün belli olmadığı(opsiyonel) bu durumda hangi tabloda çalışılacağını nereden anlıyoruz?
Context nesnemizin Set metodu bizlere generic olarak belirtilen türe uygun tabloyu getirmektedir. Dolayısıyla yukarıda “Table” ismindeki metot/lar verilen T yahut A türüne uygun DbSet nesnesini(dolayısıyla tabloyu) getirmekte ve bizlerde ona göre işlemler gerçekleştirmekteyiz. En iyisi bunu deneyimleyip, görmenizdir… Ayrıca tüm metotların [NonAction] attributeu ile işaretlenmesi sizi şaşırtmasın. Nihayetinde örneklendirme bir API alt yapısı üzerinden olduğu için ilgili metotların endpoint olmadığını ifade etmiş oluyor ve dışarıdan gelecek isteklerin es kasa bu metotlar tarafından karşılanmasını engellemiş oluyoruz.
Evet, şimdi oluşturduğumuz Repository sınıfını kullanalım.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | [ApiController] [Route("api/[controller]")] public class ArabaController : Repository<Araba> { public ArabaController(ArabaContext arabaContext) : base(arabaContext) { } public bool Ekle(Araba araba) { return Add(araba); } public bool Sil(int id) { return Remove(id); } public List<Araba> TumArabalar() { return Get(); } } |
Görüldüğü üzere Repository sınıfımız başarılı bir şekilde ilgili controller sınıfında kullanılmaktadır. Tabi burada “ArabaController” sınıfına gelen istekler neticesinde ilgili endpointler tetiklenecek Repository metotları kullanılmış olacaktır.
Ayrıca burada şu şekilde de bir kullanım sergilenebilmektedir.
Böylece Repository’den türeyen herbir sınıfın instance’i üzerinden de veritabanı işlemlerini yürütebilirsiniz.
Son söz olarak nihai usule uygun kelam etmemiz gerekirse Repository Design Pattern ORM yapılanmalarıyla(özellikle Entity Framework) oldukça basit ve bir o kadar etkili veritabanı havuzu oluşturmamızı ve tüm işlemleri tek kalemde halletmemizi sağlayan güzel ve kullanışlı bir tasarım desenidir.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Kaynak: https://www.gencayyildiz.com/blog/c-repository-design-patternrepository-tasarim-deseni
Hiç Yorum Yapılmamış. İlk Yorumu Sen Yap