Unit Of Work Design Pattern Nedir? Generic Repository ile Nasıl Kullanılır?

Müslüm Bitgen
Neyasis Technology
Published in
5 min readJun 3, 2020

Unit Of Work Design Pattern Nedir?

Kısaltması UoW olan design pattern(tasarım deseni), geliştirmelerimiz sırasında veritabanında gerçekleştirilecek olan aksiyonların anlık olarak yansıtılmasını engelleyerek, veritabanı işlemlerini bir bütün olarak kabul eder ve tek bir bağlantı üzerinden gönderir bu da performans artışı sağlar.

Unit Of Work deseni tek başına kullanılabileceği gibi, repository kurumsal tasarım deseni veya Identity Map kurumsal tasarım deseni ile de kullanılabilir.

Repository Kurumsal Tasarım Deseni

Geliştirmelerimizde repository kurumsal tasarım desenini, veritabanı işlemlerinin tekrarını engellemek, merkezden yapılması ve kullanılabilirlik prensibi çerçevesinde yapılanma sağlamak amacıyla kullanırız.

Identity Map

Bir atasözü der ki; iki saati olan bir adamın saatin kaç olduğunu asla bilmediğini söyler. İki saat kafa karıştırıcıysa, bir veritabanından olacak olan nesne yüklemesiyle daha da büyük bir karmaşa yaşayabilirsiniz. Dikkatli değilseniz, aynı veritabanı kaydındaki verileri iki farklı nesneye yükleyebilirsiniz. Daha sonra, her ikisini de güncellediğinizde, değişiklikleri veritabanına doğru bir şekil de yazmak için yeterli süreniz olacaktır. Bu bariz bir performans problemidir. Aynı verileri birden fazla yüklerseniz, uzaktan aramalarda pahalı bir ücrete maruz kalırsınız. Bu nedenle, aynı verileri iki kez yüklememek yalnızca doğruluğa yardımcı olmakla kalmaz, aynı zamanda uygulamanızı hızlandırabilir. Kimlik Haritası, tek bir iş işleminde veritabanından okunan tüm nesnelerin kaydını tutar. Bir nesneyi ne zaman isterseniz, ona sahip olup olmadığınızı görmek için önce Kimlik Haritasını kontrol edersiniz. Kaynak

Neden UnitofWork?

.NET geliştirmelerimizde kullandığımız herhangi bir ORM aracı, ekleme, silme, güncelleme gibi işlemleri bir transaction içerisinde çalıştırır. Yani her işlem için özel transaction çalıştırılır ve oluşabilecek herhangi olumsuz durumda yapılan işlem geri alınır. Bu durum performans kaybına sebep olur. Geliştirmelerimizde performansın çok önemli olduğunu bildiğimiz için bu duruma çözüm üretmek amacıyla her sorgu için transaction çalıştırmaktansa bütün sorguları kapsayan tek bir transaction devreye sokmak performans artışı sağlayacaktır. Bunun için UoW desing pattern kullanmamız gerekir.

Nasıl Kullanılır?

Örneğimiz gereği basitleştirilmiş temel bir altyapı tasarlayacağız. Bu altyapımız Entity Framework‘den yararlanarak code first yaklaşımı ile geliştireceğiz ve Repository ve Unit of Work desenlerini ise maksimum seviyede izole ederek, Entity Framework ve MsSQL çalışabilir bir şekilde modüler geliştirme yapacağız.

Örneğimiz basit bir ürün ve kategori için CRUD işlemleri yapan bir WebApi olacaktır.

BaseClass

Model katmanı içerisine ilk başta tüm entity nesnelerimiz için ortak olan OlusturmaTarihi özelliğini kalıtım yolu ile aktarabilmek için BaseClass isminde bir soyut sınıf oluşturuyoruz.

 public abstract class BaseClass
{
public DateTime OlusturmaTarihi { get; set; } = DateTime.Now;
}

Entity sınıflarımızı oluşturmaya başlayabiliriz.

Kategori sınıfımızı oluşturuyoruz

public class Kategori : BaseClass
{
public Kategori()
{
this.Urunler = new List<Urun>();
}
public int Id { get; set; }
public string Adi { get; set; }
public virtual ICollection<Urun> Urunler { get; set; }
}

Ürün sınıfımızı oluşturuyoruz

public class Urun : BaseClass
{
public int Id { get; set; }
public string Adi { get; set; }
public int KategoriId { get; set; }
public decimal BirimFiyat { get; set; }
// Relations
public virtual Kategori Kategori { get; set; }
}

Not: Modellerimizi oluştururken yukarda gördüğünüz ilişkilendirmeleri kullanabilmek için altta // Relations skobunda tanımladık.

Entities katmanını tamamladığımıza göre şimdi de DataAccess katmanımızı oluşturmaya başlayabiliriz.

UnitOfWorkPatternContext sınıfı bizim entity framework için kullanacak olduğumuz context’imiz olacaktır.

public class UnitOfWorkPatternContext : DbContext
{
public UnitOfWorkPatternContext(DbContextOptions<UnitOfWorkPatternContext>
options):base(options){}
public DbSet<Kategori> Kategoriler { get; set; }
public DbSet<Urun> Urunler { get; set; }
}

Code first olarak devam ettiğimiz için MsSQL Server üzerinde hiç bir veritabanı bulunmamaktadır. Veritabanımızı migration ile oluşturuyoruz.

Migration nasıl oluşturabiliriz?

1- Visual Studio menü sekmesinden Tools > Nuget Package Manager > Package Manager Console penceresini açıyoruz.

2-Enable-Migrations ile migration durumunu etkinleştiriyoruz.

3-Add-Migration örnek1 diyerek yeni migration oluşturuyoruz.

4-Update-Database komutu ile oluşturulan örnek1 migration’ı veritaban şemasına aktarıyoruz. Detaylı bilgiye buradan ulaşabilirsiniz.

Veritabanımız ve context’imiz oluştuğuna göre şimdi generic repository sınıfını tasarlamaya başlayabiliriz.

Not: Model katmanımızda bulunan her T tipi için aşağıda tanımladığımız fonksiyonları gerçekleştirebilecek generic bir repository tanımlıyoruz.

IRepository arayüzünü tanımlıyoruz

public interface IRepository<T> where T : class
{
IQueryable<T> GetAll();
IQueryable<T> GetAll(Expression<Func<T, bool>> predicate);
T GetById(int id);
T Get(Expression<Func<T, bool>> predicate);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
void Delete(int id);
}

EfRepository servisini tanımlıyoruz

public class EFRepository<T> : IRepository<T> where T : class
{
private readonly DbContext _dbContext;
private readonly DbSet<T> _dbSet;
public EFRepository(UnitOfWorkPatternContext dbContext)
{
_dbContext = dbContext;
_dbSet = dbContext.Set<T>();
}
public IQueryable<T> GetAll()
{
return _dbSet;
}
public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
{
return _dbSet.Where(predicate);
}
public T GetById(int id)
{
return _dbSet.Find(id);
}
public T Get(Expression<Func<T, bool>> predicate)
{
return _dbSet.Where(predicate).SingleOrDefault();
}
public void Add(T entity)
{
_dbSet.Add(entity);
}
public void Update(T entity)
{
_dbSet.Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
}
public void Delete(T entity)
{
if (entity.GetType().GetProperty("IsDelete") != null)
{
T _entity = entity;
_entity.GetType().GetProperty("IsDelete").SetValue(_entity, true);
this.Update(_entity);
}
else
{
// Önce entity'nin state'ini kontrol etmeliyiz.
var dbEntityEntry = _dbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
_dbSet.Attach(entity);
_dbSet.Remove(entity);
}
}
}
public void Delete(int id)
{
var entity = GetById(id);
if (entity == null) return;
else
{
if (entity.GetType().GetProperty("IsDelete") != null)
{
T _entity = entity;
_entity.GetType().GetProperty("IsDelete").SetValue(_entity, true);
this.Update(_entity);
}
else
{
Delete(entity);
}
}
}
}

EntityFramework için hazırladığımız bu repository’i daha önceden tasarladığımız generic repositorimiz olan IRepository arayüzünü implement ederek tasarladık.

Repositorimiz hazır olduğuna göre artık unit of work desenini tasarlamaya başlayabiliriz.

IUnitOfWork arayüzünü tanımlıyoruz

public interface IUnitOfWork : IDisposable
{
IRepository<T> GetRepository<T>() where T : class;
int SaveChanges();
}

Repository’leri bize getirecek olan GetRepository generic metodunu ve kaydetme işlemlerini toplu yapabilmemizi sağlayacak olan SaveChanges metodunu tanımlıyoruz.

EFUnitOfWork framework servisini tanımlıyoruz

public class EFUnitOfWork : IUnitOfWork
{
private readonly UnitOfWorkPatternContext _dbContext;
private bool disposed = false;
public EFUnitOfWork(UnitOfWorkPatternContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("dbContext can not be null.");

_dbContext = dbContext;
}
public IRepository<T> GetRepository<T>() where T : class
{
return new EFRepository<T>(_dbContext);
}
public int SaveChanges()
{
try
{
return _dbContext.SaveChanges();
}
catch
{
throw;
}
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_dbContext.Dispose();
}
}
this.disposed = true;
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

Unit of work pattern hazır olduğuna göre şimdi bir UrunController endpoint’lerini hazırlayalım.

Ürün endpoint’leri

[Route("api/[controller]")]
[ApiController]
public class UrunController : ControllerBase
{
private readonly IUnitOfWork _uow;
public UrunController(IUnitOfWork uow)
{
_uow = uow;
}
HttpGet]
Route("GetAllUruns")]
public ActionResult<IEnumerable<Urun>> GetAllUruns()
{
var repo = _uow.GetRepository<Urun>();
return repo.GetAll().ToList();
}
[HttpGet]
[Route("GetUrunById/{id}")]
public ActionResult<Urun> GetUrunById(int id)
{
var repo = _uow.GetRepository<Urun>();
return repo.Get(x => x.Id == id);
}
[HttpPost]
[Route("AddUrun")]
public IActionResult AddUrun([FromBody] Urun Urun)
{
var repo = _uow.GetRepository<Urun>();
repo.Add(Urun);
_uow.SaveChanges();
return Ok("Eklendi.");
}
HttpPost]
Route("DeleteUrun/{id}")]
public IActionResult DeleteUrun(int id)
{
var repo = _uow.GetRepository<Urun>();
repo.Delete(id);
_uow.SaveChanges();
return Ok("Silindi.");
}
}

Kategori endpoint’leri


[Route("api/[controller]")]
ApiController]
public class KategoriController : ControllerBase
{
private readonly IUnitOfWork _uow;
public KategoriController(IUnitOfWork uow)
{
_uow = uow;
}
[HttpGet]
[Route("GetAllKategoris")]
public ActionResult<IEnumerable<Kategori>> GetAllKategoris()
{
var repo = _uow.GetRepository<Kategori>();
return repo.GetAll().ToList();
}
[HttpGet]
[Route("GetKategoriById/{id}")]
public ActionResult<Kategori> GetKategoriById(int id)
{
var repo = _uow.GetRepository<Kategori>();
return repo.Get(x => x.Id == id);
}
[HttpPost]
[Route("AddKategori")]
public IActionResult AddKategori([FromBody] Kategori Kategori)
{
var repo = _uow.GetRepository<Kategori>();
repo.Add(Kategori);
_uow.SaveChanges();
return Ok("Eklendi.");
}
[HttpPost]
[Route("DeleteKategori/{id}")]
public IActionResult DeleteKategori(int id)
{
var repo = _uow.GetRepository<Kategori>();
repo.Delete(id);
_uow.SaveChanges();
return Ok("Silindi.");
}
}

Projeyi incelemek isterseniz Link üzerinden ulaşabilirsiniz.

Okuduğunuz için teşekkür ederim..

--

--