For faster navigation, this Iframe is preloading the Wikiwand page for Unit Of Work.

Unit Of Work

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

Ця стаття має кілька недоліків. Будь ласка, допоможіть удосконалити її або обговоріть ці проблеми на сторінці обговорення. Ця стаття містить перелік джерел, але походження окремих тверджень у ній залишається незрозумілим через практично повну відсутність виносок. Будь ласка, допоможіть поліпшити цю статтю, додайте виноски з посиланнями на відповідні джерела до тексту статті. (15 червня 2019) Ця стаття не має інтервікі-посилань. Ви можете допомогти проєкту, знайшовши та додавши їх до відповідного елементу Вікіданих. (15 червня 2019) Вступний розділ цієї статті, ймовірно, несповна підсумовує ключові тези її вмісту. Будь ласка, допоможіть розширити вступ, додавши стислий огляд найважливіших аспектів статті. (15 червня 2019)

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

Під час роботи із базою даних важливо відстежувати зміни в об'єктах, в іншому випадку дані не будуть оновлені. Це також вірно для операцій додавання та видалення.

Можна змінювати дані в сховищі при кожній взаємодії з об'єктом, але це призведе до багатьох викликів у базу даних. Очевидно це потребує підтримування транзакції відкритою, що впливає на продуктивність роботи.

Даний шаблон пропонує відстежувати всі зміни над об'єктами та вносити їх у вигляді єдиної транзакції.

Переваги та недоліки

[ред. | ред. код]
Цей розділ має вигляд переліку, який краще подати прозою. Ви можете допомогти викласти список прозою, де це доречно. Ознайомтеся з довідкою з редагування. (15 червня 2019)

Переваги

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

Недоліки

[ред. | ред. код]
  • Зростає кількість класів

Опис мовою C#

[ред. | ред. код]

Розглянемо спочатку реалізацію шаблону запропоновану Мартіном Фаулером. Вона передбачає методи для відстеження змін в об'єктах та здійснення транзакції.

public class UnitOfWork
{
    // методи для відстеження стану об'єктів
    public void RegisterAdd(object newObject)
    {
        ...
    }
    public void RegisterUpdate(object newObject)
    {
        ...
    }
    public void RegisterDelete(object newObject)
    {
        ...
    }

    // методи для здійснення транзакції
    public void Commit()
    {
        ...
    }
    public void Rollback()
    {
        ...
    }
}

Даний шаблон можна адаптувати, до мови програмування. Таким чином використаємо Entity Framework. Нехай дано деякі класи сутностей.

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

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

Запишемо інтерфейс до Unit Of Work та його реалізацію. Нам більше не потрібно відстежувати зміни у сутностях через те, що ця поведінка реалізована у Entity Framework. Але також це означає, що у нас виникнуть складнощі при зміні фреймворку.

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.

Також актуалізується проблема неможливості вибору специфічної реалізації репозиторію (наприклад, покращеної версії). Вирішити такі проблеми можна, наприклад, наступним чином:

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];
    }
...
}

У нинішніх реаліях важливо контролювати доступ багатьох потоків до репозиторію. Поряд із стандартними блокуваннями можна використати вбудовані можливості С#, зокрема клас ConcurrentDictionary: Реалізація може виглядати так:

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());
    }
...
}

Зв'язок з іншими патернами

[ред. | ред. код]

Див. також

[ред. | ред. код]

Джерела

[ред. | ред. код]
{{bottomLinkPreText}} {{bottomLinkText}}
Unit Of Work
Listen to this article

This browser is not supported by Wikiwand :(
Wikiwand requires a browser with modern capabilities in order to provide you with the best reading experience.
Please download and use one of the following browsers:

This article was just edited, click to reload
This article has been deleted on Wikipedia (Why?)

Back to homepage

Please click Add in the dialog above
Please click Allow in the top-left corner,
then click Install Now in the dialog
Please click Open in the download dialog,
then click Install
Please click the "Downloads" icon in the Safari toolbar, open the first download in the list,
then click Install
{{::$root.activation.text}}

Install Wikiwand

Install on Chrome Install on Firefox
Don't forget to rate us

Tell your friends about Wikiwand!

Gmail Facebook Twitter Link

Enjoying Wikiwand?

Tell your friends and spread the love:
Share on Gmail Share on Facebook Share on Twitter Share on Buffer

Our magic isn't perfect

You can help our automatic cover photo selection by reporting an unsuitable photo.

This photo is visually disturbing This photo is not a good choice

Thank you for helping!


Your input will affect cover photo selection, along with input from other users.

X

Get ready for Wikiwand 2.0 🎉! the new version arrives on September 1st! Don't want to wait?