ASP.NET Core & Generic Repository Pattern

Semih Elitaş
Ada Yazılım
Published in
6 min readMay 14, 2020

Herkese merhaba! Şu zor günlerde herkes gibi bir yandan yeni şeyler öğrenip, bir yandan da içimi döker gibi buraya yazmaya çalışıyorum. Yazılıma ve bir şeyler geliştirmeye yeni başlayan herkesin, geç öğrendiğine pişman olduğu, abi böyle bir şey varmış ya birinizde beni uyarmıyor yahu! dediği bir şeyler muhakkak vardır. Generic Repository Pattern ise benim için bunlardan biri. Öyleyse hadi biraz göz atalım şuna!

Nedir bu Generic Repository Pattern?

Bildiğiniz üzere her model için bazı ortak işlemler vardır. Biz bu ortak işlemlere kısaca CRUD (Create, Read, Update, Delete) işlemler diyebiliriz. Düşünün ki projemizde Book ve Product adında iki adet modelimiz var.

Bu iki model için de temel CRUD işlemlerini yazmak zorundayız:

Book modeli için CRUD İşlemler
Product modeli için CRUD İşlemler

Buraya kadar her şey normal gibi görünse de ciddi bir sorunumuz var. Modellerimiz farklı olsa da yaptığımız iş aslında aynı. Projemizde modeller/nesneler arttıkça, bu ortak metotlar her bir model için ayrı ayrı uygulanmak zorunda. Örneğin CreateBook ve CreateProduct metotları aynı veritabanı sorgularını çalıştırıp aynı işlemi yapmakta, değişen tek parametre ise bu işlemi hangi model için yaptıkları. Birisi bunu Book modeli için yaparken, diğeri Product modeli için yapmakta.

İşte bu kod tekrarına düşmemek adına, tüm modeller için kullanılabilir ortak bir yapı oluşturmalıyız. Generic Repository Pattern, “generic” kelimesinden de anlaşılacağı gibi bu“genel” yapıyı oluşturmamızı sağlıyor. Yani, ortak işlemlerimiz için genel bir yapı kurup, her bir modelin bu genel yapı üzerinden o işlemi gerçekleştirmesini sağlayacağız. Böylece, kod tekrarlarından kaçınırken, gelecekteki değişiklikler için efor sarfetmemizi gerektirmeyecek bir yapı oluşturmuş olacağız.

Evet Semih, teoriyi bırakıp pratiğe geçelim dediğinizi duyar gibiyim, o zaman haydi başlayalım!

Base Model Oluşturmakla Başlayalım!

Yine Generic mantığıyla, uygulamamızdaki modellerin id, createdAt, vb… gibi ortak olabilecek propertylerini bir araya getirecek bir Base Model oluşturuyoruz:

IEntity Arayüzü
Arayüzü Base Model olan BaseEntity’e geçiriyoruz

Ardından bu BaseEntity’den miras alacak modellerimizi/nesnelerimizi oluşturuyoruz:

Modellere özgü olan propertyler burada tanımlanacaktır. Ortak, yani genel propertyler ise üst sınıf olan BaseEntity’den devralınır.

DbContext Oluşturmak

Projemizde kullanacağımız DbContext’imizi oluşturmaya başlayalım.

Öncelikle DbContext sınıfını kullanabilmek için “Microsoft.EntityFrameworkCore” pakedini projemize dahil etmemiz gerekiyor.

Startup.cs tarafında ConfigureServices metodunda DbContext’imizi servis konteynerine ekliyoruz:

UseSqlServer fonk. için “Microsoft.EntityFrameworkCore.SqlServer” pakedini projeye dahil etmeliyiz!

DatabaseConnection adlı ConnectionString’imizi appsettings.json’da tanımlıyoruz:

DbContext’imiz hazır, haydi migration işlemlerini uygulayıp veritabanımızın son halini görelim!

PM > Add-Migration Initial

PM > Update-Database

Gördüğünüz gibi veritabanımız ve tablolarımız hazır! Haydi Generic Repository tarafına geçelim!

Generic Repository Oluşturmak

Repository kalıbımız her modelin ortak olarak ihtiyaç duyduğu metotları kapsayacak şekilde oluşturulmalıdır. GenericRepository sınıfımızın miras alması için kullanacağımız ve sadece bu ortak metotları içeren arayüzümüzü oluşturmakla başlıyoruz:

Yukarıdaki arayüzde farkedeceğiniz ilk şey generic TEntity tipidir. Bu aslında bizim için şu an veritabanındaki tablolarımıza karşılık gelmektedir (Book, Product). Örneğin, TEntity değerini Book olarak gönderdiğimizde yukarıdaki CRUD işlemlerini Book modeline göre çalıştıracağız demektir. Ayrıca TEntity’nin BaseEntity’den miras alan bir sınıf olması gerektiği konusunda bir kısıtlama getirdik, bunun nedenini birazdan anlayacağız.

Arayüzümüzü oluşturduktan sonra GenericRepository classımızı bu arayüzden miras alarak oluşturmaya başlıyoruz:

Gördüğünüz gibi miras alma işlemini tamamladık ve DbContext’i enjekte etmek için Constructor Injection (ASP.NET Core & Dependency Injection yazımda buraya detaylı değindim) kullanarak, yapıcı metoda parametre olarak geçiriyoruz.

Artık veritabanımıza bağlanarak asıl iş kodlarımızı yazma vakti geldi!

Burada değinmemiz gereken iki nokta var.

Bunlardan ilki, gördüğünüz gibi veritabanı üzerinden işlemleri yaparken hiçbir model veya tablo ismi görmüyoruz. Bunun sebebi yukarıda bahsettiğimiz, TEntity varlığının bizim modellerimize, veritabanındaki tablolalarımıza denk gelecek olmasıdır. DbContext içerisinde tanımladığımız DbSet<Book> ve DbSet<Product> nesnelerine erişmek adına, _dbContext.Set<TEntity>() gibi bir metot kullanıyoruz. TEntity, BaseEntity’nin özelliklerini miras aldığı içinde tablo/model adı vermeden ortak propertylere(id, createdAt, …) erişebiliyoruz. Bu GenericRepository’i Book veya Product için implemente ettiğimizde yukarıdaki bütün işlemleri otomatik olarak o modele göre yapacaktır.

İkinci kısım ise AsNoTracking() metodu. Bu metot, Entity Framework tarafından uygulamaların performansını optimize etmemize yardımcı olmak için geliştirilmiş bir metottur. Genel olarak sadece okuma yapacağımız, güncelleme yapmayacağımız işlemlerde AsNoTracking() ifadesini kullanırız. Üzerinde değişiklik yapıp SaveChanges() metodunu çağırdığımızda veritabanında hiçbir değişiklik olmaz. Bu da bize minimum bellek kullanımı ve optimum performans sağlayacaktır.

Yukarıdaki örneğimizde ise GetAll metodu ile sadece verileri okumak istiyoruz, yani hiçbir güncelleme yapmayacağımız bir metotta boşu boşuna veritabanını güncellemek istemiyoruz. Bu yüzden verileri çağırırken AsNoTracking() metodunu kullanıyoruz.

Generic Repository sınıflarımızı oluşturduk, artık bunları implemente etmemiz gerekiyor. Haydi bu işlemi Book için yapalım:

IBookRepository arayüzü IRepository generic arayüzünden bütün CRUD işlemlerini Book nesnesi için miras alıyor. Ayrıca CRUD işlemlerin dışında, BookRepository’e özgü bir işlem yapmak istiyorsak bu arayüzde bunu belirtebiliriz. Ben burada örnek olması adına GetFirstBookName adlı bir metot belirttim.

Ardından BookRepository implementasyonunu gerçekleştiriyoruz. Gördüğünüz gibi BookRepository içerisinde ortak işlemler (CRUD) bulunmamakta çünkü bunları Generic Repository yapısından miras alıyoruz. Bu işlemi her bir nesnemiz için gerçekleştirebiliriz ve kod tekrarına düşmeden her bir modelimizin bu işlemleri aynı yapıdan ortak olarak kullanmasını sağlayabiliriz.

Son olarak

Artık repository yapımız üzerinden işlemlerimizi deneme zamanı geldi!

Öncelikle BookRepository yapısını kodumuzun içerisinde kullanabilmemiz için DI konteynerine eklememiz gerekiyor:

Artık controller içerisine enjekte edilebilir duruma geldi:

Yapımızın çalışıp çalışmadığını kontrol etmek amaçlı hızlıca iki adet kitap oluşturdum ve bu kitapları listelemek istedim, sonuç:

Ta ta ta ta! Veriler başarıyla geldi.

Listeleme işlemini Generic Repository yapısını kullanarak, “First Book: Dönüşüm” yazısını ise BookRepository için özel olarak yazdığımız GetFirstBookName metodunu kullanarak getirdik.

Ve ben bu listeleme işlemi için bütün kitapları listeleyen özel bir fonksiyon yazmadım, generic yapıdaki GetAll() fonksiyonum ile bunu Book nesnesi için ayrı bir fonksiyon yazmadan hallettim. Bir de bunu artık bütün CRUD işlemleri için yapabildiğimizi ve sadece Book nesnesi için değil bütün nesneler için bunu kullanabileceğimizi düşünün. Böylece inanılmaz bir kod tekrarının önüne geçmiş olduk.

Hangimiz kod tekrarına düşmedik çılgınlar gibi!

References

https://codingblast.com/entity-framework-core-generic-repository/

--

--