Паттерн Unit of Work

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

В приложениях ASP.NET MVC нередко используется паттерн репозиторий для инкапсулирования логики работы с источниками данных. И нередко мы оперируем множеством сущностей и моделей, для управления которыми создается также множество классов-репозиториев. Паттерн Unit of Work позволяет упростить работу с различными репозиториями и дает уверенность, что все репозитории будут использовать один и тот же контекст данных.

Рассмотрим пример. Допустим, у нас есть следующая пара моделей:

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public string Number { get; set; }
    public int BookId { get; set; }
    public Book Book { get; set; }
}

И также имеется следующий контекст данных:

public class OrderContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Order> Orders { get; set; }
}

Если мы будем использовать паттерн Репозиторий для работы с базой данных, то мы можем определить интерфейс репозитория и создать две его отдельных реализации:

interface IRepository<T> where T : class
{
    IEnumerable<T> GetAll();
    T Get(int id);
    void Create(T item);
    void Update(T item);
    void Delete(int id);
}

public class BookRepository : IRepository<Book>
{
    private OrderContext db;

    public BookRepository(OrderContext context)
    {
        this.db = context;
    }

    public IEnumerable<Book> GetAll()
    {
        return db.Books;
    }

    public Book Get(int id)
    {
        return db.Books.Find(id);
    }

    public void Create(Book book)
    {
        db.Books.Add(book);
    }

    public void Update(Book book)
    {
        db.Entry(book).State = EntityState.Modified;
    }

    public void Delete(int id)
    {
        Book book = db.Books.Find(id);
        if (book != null)
            db.Books.Remove(book);
    }
}

public class OrderRepository : IRepository<Order>
{
    private OrderContext db;

    public OrderRepository(OrderContext context)
    {
        this.db = context;
    }

    public IEnumerable<Order> GetAll()
    {
        return db.Orders.Include(o=>o.Book);
    }

    public Order Get(int id)
    {
        return db.Orders.Find(id);
    }

    public void Create(Order order)
    {
        db.Orders.Add(order);
    }

    public void Update(Order order)
    {
        db.Entry(order).State = EntityState.Modified;
    }

    public void Delete(int id)
    {
        Order order = db.Orders.Find(id);
        if (order != null)
            db.Orders.Remove(order);
    }
}

Для того, чтобы использовать паттерн Unit of Work, создадим новый класс:

public class UnitOfWork : IDisposable
{
    private OrderContext db = new OrderContext();
    private BookRepository bookRepository;
    private OrderRepository orderRepository;

    public BookRepository Books
    {
        get
        {
            if (bookRepository == null)
                bookRepository = new BookRepository(db);
			return bookRepository;
        }
    }

    public OrderRepository Orders
    {
        get
        {
            if (orderRepository == null)
                orderRepository = new OrderRepository(db);
            return orderRepository;
        }
    }

    public void Save()
    {
        db.SaveChanges();
    }

    private bool disposed = false;

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

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

Класс UnitOfWork предоставляет доступ к репозиториям через отдельные свойства и определяет общий контекст для обоих репозиториев.

Кроме того, данный класс содержит дополнительные методы Save() и Dispose(), которые в иной ситуации мы могли б определить в репозиториях. Но так как этот функционал будет общим для обоих репозиториев, то его лучше вынести в класс UnitOfWork.

В этом случае контроллер, который производит операции над моделью Book, мог бы выглядеть следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using UoWMvcApp.Models;

namespace UoWMvcApp.Controllers
{
    public class HomeController : Controller
    {
        UnitOfWork unitOfWork;
        public HomeController()
        {
            unitOfWork = new UnitOfWork();
        }
        public ActionResult Index()
        {
            var books = unitOfWork.Books.GetAll();
            return View();
        }

        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(Book b)
        {
            if(ModelState.IsValid)
            {
                unitOfWork.Books.Create(b);
                unitOfWork.Save();
                return RedirectToAction("Index");
            }
            return View(b);
        }

        public ActionResult Edit(int id)
        {
            Book b = unitOfWork.Books.Get(id);
            if (b == null)
                return HttpNotFound();
            return View(b);
        }

        [HttpPost]
        public ActionResult Edit(Book b)
        {
            if (ModelState.IsValid)
            {
                unitOfWork.Books.Update(b);
                unitOfWork.Save();
                return RedirectToAction("Index");
            }
            return View(b);
        }

        public ActionResult Delete(int id)
        {
            unitOfWork.Books.Delete(id);
            unitOfWork.Save();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            unitOfWork.Dispose();
            base.Dispose(disposing);
        }
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850