Ця стаття має кілька недоліків. Будь ласка, допоможіть удосконалити її або обговоріть ці проблеми на .
|
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()); } ... }
Зв'язок з іншими патернами
- Unit Of Work та Repository часто[] використовують в парі.
Див. також
Джерела
- Implementing the repository and unit of work patterns [ 18 червня 2019 у Wayback Machine.]
- Repository and unit of work pattern [ 10 серпня 2020 у Wayback Machine.]
- Common mistakes with the repository pattern [ 13 червня 2019 у Wayback Machine.]
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Cya stattya maye kilka nedolikiv Bud laska dopomozhit udoskonaliti yiyi abo obgovorit ci problemi na Cya stattya mistit perelik posilan ale pohodzhennya tverdzhen u nij zalishayetsya nezrozumilim cherez praktichno povnu vidsutnist vnutrishnotekstovih dzherel vinosok Bud laska dopomozhit polipshiti cyu stattyu peretvorivshi dzherela z pereliku posilan na dzherela vinoski u samomu teksti statti 15 chervnya 2019 Cya stattya ne maye interviki posilan Vi mozhete dopomogti proyektu znajshovshi ta dodavshi yih do vidpovidnogo elementu Vikidanih 15 chervnya 2019 Vstupnij rozdil ciyeyi statti jmovirno nespovna pidsumovuye klyuchovi tezi yiyi vmistu Bud laska dopomozhit rozshiriti vstup dodavshi stislij oglyad najvazhlivishih aspektiv statti 15 chervnya 2019 Unit Of Work patern ob yektno relyacijnoyi povedinki meta yakogo polyagaye u vidstezhuvanni zmini ob yektiv pid chas tranzakciyi Chasto vikoristovuyetsya iz paternom Repository Pid chas roboti iz bazoyu danih vazhlivo vidstezhuvati zmini v ob yektah v inshomu vipadku dani ne budut onovleni Ce takozh virno dlya operacij dodavannya ta vidalennya Mozhna zminyuvati dani v shovishi pri kozhnij vzayemodiyi z ob yektom ale ce prizvede do bagatoh viklikiv u bazu danih Ochevidno ce potrebuye pidtrimuvannya tranzakciyi vidkritoyu sho vplivaye na produktivnist roboti Danij shablon proponuye vidstezhuvati vsi zmini nad ob yektami ta vnositi yih u viglyadi yedinoyi tranzakciyi Perevagi ta nedolikiCej rozdil maye viglyad pereliku yakij krashe podati prozoyu Vi mozhete dopomogti viklasti spisok prozoyu de ce dorechno Oznajomtesya z dovidkoyu z redaguvannya 15 chervnya 2019 Perevagi UnitOfWork poklikanij vidstezhuvati vsi zmini danih yaki mi zdijsnyuyemo z domennoyu modellyu v ramkah biznes tranzakciyi Pislya togo yak biznes tranzakciya zakrivayetsya vsi zmini potraplyayut v BD u viglyadi yedinoyi tranzakciyi Fabrika dlya repozitoriyiv Liniva inicializaciya repozitoriyiv zalezhno vid realizaciyi Zabezpechuye vikoristannya odnogo z yednannya do BD usima repozitoriyami Nedoliki Zrostaye kilkist klasivOpis movoyu C Rozglyanemo spochatku realizaciyu shablonu zaproponovanu Martinom Faulerom Vona peredbachaye metodi dlya vidstezhennya zmin v ob yektah ta zdijsnennya tranzakciyi public class UnitOfWork metodi dlya vidstezhennya stanu ob yektiv public void RegisterAdd object newObject public void RegisterUpdate object newObject public void RegisterDelete object newObject metodi dlya zdijsnennya tranzakciyi public void Commit public void Rollback Danij shablon mozhna adaptuvati do movi programuvannya Takim chinom vikoristayemo Entity Framework Nehaj dano deyaki klasi sutnostej public class User public class Apartment public class Bill A takozh pripustimo sho patern Repository dlya cih sutnostej vzhe realizovanij Zapishemo interfejs do Unit Of Work ta jogo realizaciyu Nam bilshe ne potribno vidstezhuvati zmini u sutnostyah cherez te sho cya povedinka realizovana u Entity Framework Ale takozh ce oznachaye sho u nas viniknut skladnoshi pri zmini frejmvorku public interface IUnitOfWork IUserRepository UserRepository get IApartmentRepository ApartmentRepository get IBillRepository BillRepository get void Update lt TEntity gt TEntity entityToUpdate where TEntity class int Save Najprostisha realizaciya matime nastupnij viglyad public class UnitOfWork IUnitOfWork polya private readonly DbContext dataBaseContext private readonly IUserRepository userRepository private readonly IApartmentRepository apartmentRepository private readonly IBillRepository billRepository konstruktori public UnitOfWork DbContext dataBaseContext this dataBaseContext dataBaseContext this userRepository new UserRepository dataBaseContext this apartmentRepository new ApartmentRepository dataBaseContext this billRepository new BillRepository dataBaseContext vlastivosti public IUserRepository UserRepository gt userRepository public IApartmentRepository ApartmentRepository gt apartmentRepository public IBillRepository BillRepository gt billRepository metodi public int Save return dataBaseContext SaveChanges public void Update lt TEntity gt TEntity entityToUpdate where TEntity class if dataBaseContext Entry entityToUpdate State EntityState Detached dataBaseContext Set lt TEntity gt Attach entityToUpdate dataBaseContext Entry entityToUpdate State EntityState Modified Nedolik polyagaye u stvorenni vsih repozitoriyiv pri inicializaciyi Unit Of Work Zabezpechimo yih linivu inicializaciyu Perepishemo vlastivosti nastupnim chinom public IUserRepository UserRepository get if userRepository null userRepository new UserRepository dataBaseContext return userRepository Yaksho z yavlyayetsya neobhidnist dodati novu sutnist dovedetsya kozhnij raz redaguvati klas ta interfejs sho vmishyuyut logiku Unit Of Work Takozh aktualizuyetsya problema nemozhlivosti viboru specifichnoyi realizaciyi repozitoriyu napriklad pokrashenoyi versiyi Virishiti taki problemi mozhna napriklad nastupnim chinom public interface IUnitOfWork TRepository GetRepository lt TEntity TRepository gt where TEntity class where TRepository IRepository lt TEntity gt new void Update lt TEntity gt TEntity entityToUpdate where TEntity class int Save public class UnitOfWork IUnitOfWork polya private readonly DbContext dataBaseContext private readonly IDictionary lt Type object gt repositoriesFactory konstruktori public UnitOfWork DbContext dataBaseContext this dataBaseContext dataBaseContext this repositoriesFactory new Dictionary lt Type object gt metodi public TRepository GetRepository lt TEntity TRepository gt where TEntity class where TRepository IRepository lt TEntity gt 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 U ninishnih realiyah vazhlivo kontrolyuvati dostup bagatoh potokiv do repozitoriyu Poryad iz standartnimi blokuvannyami mozhna vikoristati vbudovani mozhlivosti S zokrema klas ConcurrentDictionary Realizaciya mozhe viglyadati tak public class UnitOfWork IUnitOfWork polya private readonly DbContext dataBaseContext private readonly ConcurrentDictionary lt Type object gt repositoriesFactory konstruktori public UnitOfWork DbContext dataBaseContext this dataBaseContext dataBaseContext this repositoriesFactory new ConcurrentDictionary lt Type object gt metodi public TRepository GetRepository lt TEntity TRepository gt where TEntity class where TRepository IRepository lt TEntity gt new Type key typeof TEntity add repo lazy loading return TRepository repositoriesFactory GetOrAdd key factory gt TRepository repository new TRepository repository SetDbContext dataBaseContext return repository Zalishilos virishiti te sho metod GetRepository pracyuye iz realizaciyeyu a ne abstrakciyeyu Skoristayemos fabrikoyu public class UnitOfWork IUnitOfWork polya private readonly DbContext dataBaseContext private readonly ConcurrentDictionary lt Type object gt repositories private readonly IDictionary lt Type Func lt object gt gt repositoriesFactory konstruktori public UnitOfWork DbContext dataBaseContext this dataBaseContext dataBaseContext this repositoriesFactory InitializeRepositoriesFactory this repositories new ConcurrentDictionary lt Type object gt private IDictionary lt Type Func lt object gt gt InitializeRepositoriesFactory return new Dictionary lt Type Func lt object gt gt typeof IUserRepository gt new UserRepository dataBaseContext public void RegisterRepositoriesFromAssembly Assembly assembly private void RegisterRepository Type key IRepository repository metodi public TRepositoryInterface GetRepository lt TRepositoryInterface gt Type key typeof TRepositoryInterface return TRepositoryInterface repositories GetOrAdd key repositoriesFactory key Invoke Zv yazok z inshimi paternamiUnit Of Work ta Repository chasto dzherelo vikoristovuyut v pari Div takozhShabloni proyektuvannya programnogo zabezpechennya Ob yektno oriyentovane programuvannyaDzherelaImplementing the repository and unit of work patterns 18 chervnya 2019 u Wayback Machine Repository and unit of work pattern 10 serpnya 2020 u Wayback Machine Common mistakes with the repository pattern 13 chervnya 2019 u Wayback Machine