Herkese merhaba,
Bu yazımda Dapper Generic Repository yapısını nasıl kuracağımıza değineceğim. Mevcut bir ORM aracını değiştirip Dapper için de generic repository yapmak isteyebiliriz. İnternette Dapper Generic Repository yapısını dinamik görmemem; bu yazıyı yazmamda motivasyon kaynağı oldu.
Ön bilgi olması açısından kullanacağımız nuget paketimiz, Dapper.Contrib'dir. Bu paket, temel CRUD işlemleri için helper metotlar içerir. Paketin içeriğinde Dapper paketi de mevcuttur. Dapper kütüphanesi de kendiliğinden gelecektir.
Dapper.Contrib paketini resmi dokümantasyonunu detaylı incelemek için linke tıklayınız.
Ayrıca Dapper.Contrib.Extensions nuget paketini de entity model sınıflarında veri tabanı tablosunu eşleştrmek ve primary key olan property için annotation eklemek için de yüklememiz gerekir.
Paketlerimizi anladıktan sonra Generic Repository yapısını kurmaya başlayabiliriz. Temel CRUD operasyonlarımızı içerecek generic bir IEntityRepository interface oluşturuyoruz. Farklı ORM ve teknolojilerde uygun geliştirme yapılabilir. Bu yazı özelinde Dapper için bu interfacedeki metotları kullanacak bir sınıf oluşturacağız.
public interface IEntityRepository<T> where T : class, IEntity, new() | |
{ | |
List<T> GetAll(Expression<Func<T, bool>> filter = null); | |
void Add(T entity); | |
void Update(T entity, Expression<Func<T, bool>> filter = null); | |
void Delete(T entity, Expression<Func<T, bool>> filter = null); | |
T Get(Expression<Func<T, bool>> filter); | |
} |
DapperGenericRepository sınıfı da IEntityRepository interfacesini miras alacak generic bir yapıdadır. Metotları implement ederken, Dapper.Contrib.Extensions paketindeki metotları kullandık.
public class DapperGenericRepository<TEntity> : IEntityRepository<TEntity> where TEntity : class, IEntity, new() | |
{ | |
public void Add(TEntity entity) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
connection.Insert(entity); | |
} | |
} | |
public void Delete(TEntity entity, Expression<Func<TEntity, bool>> filter = null) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
connection.Delete(entity); | |
} | |
} | |
public TEntity Get(Expression<Func<TEntity, bool>> filter) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
return connection.GetAll<TEntity>().Where(filter.Compile()).SingleOrDefault(); | |
} | |
} | |
public List<TEntity> GetAll(Expression<Func<TEntity, bool>> filter = null) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
return filter == null ? | |
connection.GetAll<TEntity>().ToList() : | |
connection.GetAll<TEntity>().Where(filter.Compile()).ToList(); | |
} | |
} | |
public void Update(TEntity entity, Expression<Func<TEntity, bool>> filter = null) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
connection.Update(entity); | |
} | |
} | |
} |
Metotları oluştururken using bloklarında, önceden oluşturduğum DbConnect sınıfındaki Connection propertysini kullandım. İşlevi, appsettings.json dosyasını build edip connection stringi almaya yarar. Connection oluşturduktan sonra Dapper sorgularını yapabiliyoruz. Connection Stringimizde veri tabanı olarak Northwind kullandık.
public class DbConnect | |
{ | |
public static SqlConnection Connection => new SqlConnection(ConnectionString); | |
public static string ConnectionString | |
{ | |
get | |
{ | |
var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); | |
return config.GetConnectionString("DefaultConnection"); | |
} | |
} | |
} |
Generic Repository yapısını oluşturduktan sonra kullanım aşamasına geçiyoruz. DataAccess katmanında Abstract interface ve Concrete sınıflar oluşturuyoruz. Concrete sınıflar, kullanacağımız ORM veya teknolojilere göre değişebilir. Dapper için veri erişim işlemleri, Dapper klasörü altında yapılacaktır. Her bir entity için sınıflar oluşturuldu.
public interface IProductDal : IEntityRepository<Product> | |
{ | |
List<Product> GetProductListByCategoryId(int categoryId); | |
} |
DapperProductDal ise IProductDal interfacesinde eklenen metotların Dapper ORM'e göre doldurulduğu yerdir. Metot, Dapper'daki Query metodu kullanılarak GetAllProductsByCategoryId stored procedure ismi Query metodunda ilk parametre olarak gönderiliyor. Bir sonraki parametreye gönderilen kategori id'ye göre ürünlerin listelenmesi yapılacaktır.
public class DapperProductDal : DapperGenericRepository<Product>, IProductDal | |
{ | |
public List<Product> GetProductListByCategoryId(int categoryId) | |
{ | |
using (var connection = DbConnect.Connection) | |
{ | |
return connection.Query<Product>("GetAllProductsByCategoryId @CategoryId", | |
new { CategoryId = categoryId }) | |
.ToList(); | |
} | |
} | |
} |
DataAccess katmanında oluşturulan metotları, Business katmanında çağıracağız. IProductService interfacesinden miras alan ProductManager sınıfı, bizim Product entity için iş yapan metotları barındıracaktır. IProductDal ile bağımlılık enjekte edilecektir.
public interface IProductService | |
{ | |
List<Product> GetAll(); | |
List<Product> GetProductListByCategoryId(int categoryId); | |
void Add(Product product); | |
void Update(Product product, Expression<Func<Product, bool>> filter=null); | |
void Delete(Product product, Expression<Func<Product, bool>> filter = null); | |
Product GetById(Expression<Func<Product, bool>> filter = null); | |
} |
ProductManager.cs implementasyonu görünümü aşağıdaki gibidir.
public class ProductManager : IProductService | |
{ | |
private IProductDal _productDal; | |
public ProductManager(IProductDal productDal) | |
{ | |
_productDal = productDal; | |
} | |
public void Add(Product product) | |
{ | |
_productDal.Add(product); | |
} | |
public void Update(Product product, Expression<Func<Product, bool>> filter = null) | |
{ | |
_productDal.Update(product, filter); | |
} | |
public void Delete(Product product, Expression<Func<Product, bool>> filter = null) | |
{ | |
_productDal.Delete(product, filter); | |
} | |
public List<Product> GetAll() | |
{ | |
return _productDal.GetAll(); | |
} | |
public Product GetById(Expression<Func<Product, bool>> filter = null) | |
{ | |
return _productDal.Get(filter); | |
} | |
public List<Product> GetProductListByCategoryId(int categoryId) | |
{ | |
return _productDal.GetProductListByCategoryId(categoryId); | |
} | |
} |
Models katmanında entity objelerimiz bulunuyor. Northwind veri tabanındaki tablolar için sınıflar oluşturuldu. Products tablosuna karşılık gelen Product sınıfı oluşturuldu.
using Dapper.Contrib.Extensions; | |
namespace Models | |
{ | |
[Table("Products")] | |
public class Product : IEntity | |
{ | |
[Key] | |
public int ProductId { get; set; } | |
public string ProductName { get; set; } | |
public int CategoryId { get; set; } | |
public string QuantityPerUnit { get; set; } | |
public decimal UnitPrice { get; set; } | |
public short UnitsInStock { get; set; } | |
} | |
} |
Örneklerde Key ve Table olarak açıklanan attributeler, Dapper.Contrib paketinde Data Annotations olarak geçer ve mapping işleminin doğru bir şekilde yapılmasını sağlar.
UnitTest'te DapperProductTests sınıfında Product için örnek testler hazırlandı. Aşağıdaki ekran görüntülerinde çıktıları görebilirsiniz.
[TestClass] | |
public class DapperProductTests | |
{ | |
public IProductService productService; | |
public IProductDal productDal; | |
public DapperProductTests() | |
{ | |
productService = new ProductManager(new DapperProductDal()); | |
} | |
[TestMethod] | |
public void Should_Return_AllProducts_Successful() | |
{ | |
var result = productService.GetAll(); | |
} | |
[TestMethod] | |
public void Should_Return_AllProducts_ByCategoryId_Successful() | |
{ | |
var categoryId = 4; | |
var result = productService.GetProductListByCategoryId(categoryId); | |
} | |
[TestMethod] | |
public void Should_Return_SingleProduct_ById_Successful() | |
{ | |
var result = productService.GetById(p=>p.ProductId==5); | |
} | |
} |
Dapper ORM için Generic Repository yapısının tamamını .NET 6 üzerinde çalışan örnek repomda bulabilirsiniz.
Yorumlar
Yorum Gönder