Unit of Work به معنای واحد کار است و یک نوع الگوی طراحی است. بر اساس این الگو نباید در هر قسمت از برنامه شئ ای از روی DbContext بسازیم و فقط یک بار باید از روی DbContext شی جدید ساخت. به عبارت دیگر برنامه نویس در لایه ی Business Logic نباید با Repository های مختلف کار کند و این وظایف باید به Unit of Work سپرده شود و برنامه نویس فقط از آن استفاده کند. می توان از Unit of Work به عنوان یک Facade از Repository ها یاد کرد.
طبق این الگو، تمام درخواست های کار با دیتابیس در یک صف قرار می گیرند و پشت سر هم انجام می شوند. بعد از آنکه تمام این درخواست ها با موفقیت اجرا شدند، دستور SaveChanges اجرا می شود. چون این الگوی طراحی از مفهوم Transaction استفاده می کند، اگر در این بین مشکلی پیش بیاید یا یکی از درخواست ها با خطا مواجه شود تمام درخواست ها لغو می شوند و هیچ تغییری در دیتابیس رخ نخواهد داد و در اصطلاح Rollback اتفاق می افتد.
این روش مزیت های بسیاری دارد، مثلا Connection های کمتری به دیتابیس وجود خواهند داشت چون در Unit of Work یک تراکنش به ازای چند عمل وجود دارد و نه یک تراکنش به ازای هر عمل.

چرا Unit Of Work ؟
- عدم دسترسی مستقیم Controller به دیتابیس هنگام عملیات CRUD. چون ارتباط، با استفاده از کلاس و اینترفیس مربوط به Unit Of Work برقرار می شود.
- افزایش امنیت دیتابیس
- بهینگی سیستم و جلوگیری از خطاهای بالقوه دیتابیسی( اگر درخواستی نیاز به چندین بار ارتباط با دیتابیس داشته باشد، در هر ارتباط یک نمونه از دیتابیس در سرور ساخته می شود که اگر تعداد درخواست ها بالا باشد سرعت دیتابیس و سرور کند می شود.)
مثال:
مثال دو Model به نام های Person و Category داریم و می خواهیم به جای آنکه از Repository هر کدام از آنها استفاده کنیم از یک Context واحد استفاده کنیم. بهتر است قبل از شروع کار نگاهی به این Model ها بیاندازیم.
کلاس Base Entity
1 2 3 4 5 6 7 8 |
public class BaseEntity { [Key] public int Id { get; set; } [Required] public string Name { get; set; } } |
کلاس Person Model
1 2 3 4 5 6 7 8 |
public class PersonModel : BaseEntity { public string Age { get; set; } public string Address { get; set; } public int CategoryModelId { get; set; } public CategoryModel CategoryModel { get; set; } } |
کلاس Category Model
1 2 3 4 |
public class CategoryModel:BaseEntity { public string CategoryIcon { get; set; } } |
ابتدا باید یک پوشه ی مستقل به نام Repository ایجاد نماییم و در آن یک اینترفیس به نام IRepository بسازیم.

سپس باید کلاس Repository را بنویسیم که اینترفیس IRepository را پیاده سازی می کند.

سپس باید اینترفیس IPersonRepository را بنویسیم.

و کلاسی به نام SqlPersonRepository که آن را پیاده سازی کند.

اینترفیس ICategoryRepository

اگر دقت کنید می بینید که چیزی در این اینترفیس نوشته نشده است. دلیلش این است که Repository مربوط به Category تنها از همان متدهای اصلی موجود در اینترفیس IRepository استفاده می کند. کلاسی که این اینترفیس را پیاده سازی می کند نیز به شکل زیر خواهد بود.

سپس باید یک اینترفیس به نام IUnitOfWork بسازیم. این اینترفیس به شکل زیر خواهد بود.
1 2 3 4 5 6 7 8 9 |
public interface IUnitOfWork : IDisposable { void Save(); void Commit(); void Rollback(); IPersonRepository PersonRepository { get; } ICategoryRepository CategoryRepository { get; } } |
بعد از آن باید کلاس Unit of Work را بنویسیم که اینترفیس IUnitOfWork را پیاده سازی می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class UnitOfWork : IUnitOfWork { AppDbContext _context; IDbContextTransaction _transaction; public IPersonRepository PersonRepository => new SqlPersonRepository(_context); public ICategoryRepository CategoryRepository => new SqlCategoryRepository(_context); public UnitOfWork(AppDbContext context) { _context = context; _transaction = _context.Database.BeginTransaction(); } public void Commit() { _transaction.Commit(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Rollback() { _transaction.Rollback(); } public void Save() { _context.SaveChanges(); } protected virtual void Dispose(bool disposing) { _context?.Dispose(); } } |
و در کنترلر، به جای آن که Repository های مختلف را Inject کنیم، تنها کافی است که اینترفیس IUnitOfWork را Inject نماییم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public class PersonController : Controller { IUnitOfWork UnitOfWork { get; } public PersonController(IUnitOfWork unitOfWork) { UnitOfWork = unitOfWork; } public IActionResult Index() { var persons = UnitOfWork.PersonRepository.GetAllPersons(); //some code return View(); } [HttpGet] public IActionResult Details(int id) { var _personModel = UnitOfWork.PersonRepository.GetPerson(id); //some code return View(); } [HttpGet] public ViewResult Create() { //some code return View(); } [HttpPost] public IActionResult Create(PersonCreateViewModel person) { if (ModelState.IsValid) { try { //some code var newPerson = UnitOfWork.PersonRepository.Add(newPersonModel); UnitOfWork.Save(); UnitOfWork.Commit(); return RedirectToAction("details", new { id = newPerson.Id }); } catch { UnitOfWork.Rollback(); } } return View(person); } |
و در Startup.cs در متد ConfigureServices کد زیر را باید بنویسیم.
