Unit Of Work

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

Unit Of Work — патерн, об'єктно-реляційної поведінки. Часто використовується із патерном Repository.

Переваги та недоліки[ред. | ред. код]

Переваги[ред. | ред. код]

  • UnitOfWork покликаний відстежувати всі зміни даних, які ми здійснюємо з доменною моделлю в рамках бізнес-транзакції. Після того, як бізнес-транзакція закривається, всі зміни потрапляють в БД у вигляді єдиної транзакції.
  • Фабрика для репозиторіїв
  • Лінива ініціалізація репозиторіїв (залежно від реалізації)
  • Забезпечує використання одного з'єднання до БД усіма репозиторіями

Недоліки[ред. | ред. код]

  • Зростає кількість класів

Опис мовою C#[ред. | ред. код]

Використаємо Entity Framework. Нехай дано деякі класи сутностей.

public class User { ... }
public class Apartment { ... }
public class Bill { ... }

А також припустимо, що патерн Repository для цих сутностей вже реалізований.

Запишемо інтерфейс до Unit Of Work та його реалізацію.

public interface IUnitOfWork
{
    IUserRepository UserRepository { get; }
    IApartmentRepository ApartmentRepository { get; }
    IBillRepository BillRepository { get; }

    void Update<TEntity>(TEntity entityToUpdate) where TEntity : class;
    int Save();
}

Найпростіша реалізація матиме наступний вигляд:

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;

    private readonly IUserRepository userRepository;
    private readonly IApartmentRepository apartmentRepository;
    private readonly IBillRepository billRepository;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;

        this.userRepository = new UserRepository(dataBaseContext);
        this.apartmentRepository = new ApartmentRepository(dataBaseContext);
        this.billRepository = new BillRepository(dataBaseContext);
    }

    // властивості
    public IUserRepository UserRepository => userRepository;
    public IApartmentRepository ApartmentRepository => apartmentRepository;
    public IBillRepository BillRepository => billRepository;

    // методи
    public int Save()
    {
        return dataBaseContext.SaveChanges();
    }

    public void Update<TEntity>(TEntity entityToUpdate) where TEntity : class
    {
        if (dataBaseContext.Entry(entityToUpdate).State == EntityState.Detached)
        {
            dataBaseContext.Set<TEntity>().Attach(entityToUpdate);
        }
        dataBaseContext.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

Недолік полягає у створенні всіх репозиторіїв при ініціалізації Unit Of Work. Забезпечимо їх ліниву ініціалізацію. Перепишемо властивості наступним чином:

...
    public IUserRepository UserRepository
    {
        get
        {
            if (userRepository == null) userRepository = new UserRepository(dataBaseContext);
            return userRepository;
        }
    }
...

Якщо кількість сутностей, велика, або потрібно добавити нову сутність нам доведеться, редагувати клас Unit Of Work. Також ми не можемо вибрати яка саме реалізація репозиторію нам потрібна. Спробуємо вирішити ці 2 проблеми:

public interface IUnitOfWork
{
    TRepository GetRepository<TEntity, TRepository>()
        where TEntity : class
        where TRepository : IRepository<TEntity>, new();

    void Update<TEntity>(TEntity entityToUpdate) where TEntity : class;
    int Save();
}

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly IDictionary<Type, object> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        this.repositoriesFactory = new Dictionary<Type, object>();
    }
    
    // методи
    public TRepository GetRepository<TEntity, TRepository>()
            where TEntity : class
            where TRepository : IRepository<TEntity>, new()
    {
        Type key = typeof(TEntity);

        // add repo, lazy loading
        if (!repositoriesFactory.ContainsKey(key))
        {
            TRepository repository = new TRepository();
            repository.SetDbContext(dataBaseContext);

            repositoriesFactory.Add(key, repository);
        }

        // return repository
        return (TRepository)repositoriesFactory[key];
    }
...
}

Вирішимо проблему паралельного доступу до репозиторію:

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly ConcurrentDictionary<Type, object> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        this.repositoriesFactory = new ConcurrentDictionary<Type, object>();
    }
    
    // методи
    public TRepository GetRepository<TEntity, TRepository>()
            where TEntity : class
            where TRepository : IRepository<TEntity>, new()
    {
        Type key = typeof(TEntity);

        // add repo, lazy loading
        return (TRepository)repositoriesFactory.GetOrAdd(key, factory =>
        {
            TRepository repository = new TRepository();
            repository.SetDbContext(dataBaseContext);

            return repository;
        });
    }
...
}

Залишилось вирішити, те що метод GetRepository працює із реалізацією, а не абстракцією. Скористаємось фабрикою

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly ConcurrentDictionary<Type, object> repositories;
    private readonly IDictionary<Type, Func<object>> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        
        this.repositoriesFactory = InitializeRepositoriesFactory();
        this.repositories = new ConcurrentDictionary<Type, object>();
    }
    
    private IDictionary<Type, Func<object>> InitializeRepositoriesFactory()
    {
        return new Dictionary<Type, Func<object>>()
        {
            [typeof(IUserRepository)] = () => new UserRepository(dataBaseContext),
                                     .    .    .
        };
    }

    public void RegisterRepositoriesFromAssembly(Assembly assembly)
    {
                                     .    .    .
    }

    private void RegisterRepository(Type key, IRepository repository)
    {
                                     .    .    .
    }

    // методи
    public TRepositoryInterface GetRepository<TRepositoryInterface>()
    {
        Type key = typeof(TRepositoryInterface);
            
        return (TRepositoryInterface)repositories.GetOrAdd(key, repositoriesFactory[key].Invoke());
    }
...
}

Зв'язок з іншими патернами[ред. | ред. код]

Див. також[ред. | ред. код]

Джерела[ред. | ред. код]