Repository — Вікіпедія
![]() | Ця стаття має кілька недоліків. Будь ласка, допоможіть удосконалити її або обговоріть ці проблеми на сторінці обговорення.
|
![](http://upload.wikimedia.org/wikipedia/commons/a/ad/UML_%D0%B4%D1%96%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%B0_%D0%BA%D0%BB%D0%B0%D1%81%D1%96%D0%B2_%D0%BF%D1%80%D0%B8_%D1%80%D0%B5%D0%B0%D0%BB%D1%96%D0%B7%D0%B0%D1%86%D1%96%D1%8E_%D0%BF%D0%B0%D1%82%D0%B5%D1%80%D0%BD%D1%83_Repository.png)
Repository — патерн, який розділяє рівні джерела даних і логіки програми. Часто використовується із патерном Unit Of Work
![]() | Цей розділ має вигляд переліку, який краще подати прозою. (15 червня 2019) |
- Використовується, як колекція
- Інкапсулює великі запити до БД в методи
- Рівень абстракції між Business Logic рівнем та Data Access рівнем
- Ізолює програму від змін джерела даних
- Джерело даних може бути змінено без будь-яких змін в бізнес логіці і з мінімальними змінами в Репозиторії
- Полегшує автоматизоване юніт тестування, Test Driven Development
- Легко створювати mock репозиторію
- Зростає кількість класів
- Погіршує продуктивність
- Обмежує у використанні особливостей ОРМ фреймворку
Використаємо Entity Framework. Нехай дано клас-сутність User
public class User { public int Id { get; set; } public string Name { get; set; } }
Тепер напишемо інтерфейс репозиторію. Варто зазначити, що репозиторій є колекцією, і має поводитись як колекція, та не містити методів Update(), Save() тощо. Також однією із великих помилок, є те що методи повертають IQueryable замість IEnumerable. Якщо повертати IQueryable це дозволить надбудувати над запитом, ще запити, що не є вірним, оскільки мета цього патерну якраз і є уникнення великих запитів. В такому разі, краще написати ще один метод, який буде виконувати більший запит.
public interface IRepository<TEntity> where TEntity : class { int Count(); int Count(Expression<Func<TEntity, bool>> predicate); IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "", int? page = null, int? amount = null); TEntity Get(int id); void Insert(TEntity entity); void Delete(object id); void Delete(TEntity entityToDelete); void Delete(Expression<Func<TEntity, bool>> predicate); }
Тепер реалізуємо цей інтерфейс у вигляді узагальненого класу. При реалізації ми повертаємо сутність, а не DTO. Мапування — це не відповідальність репозиторію.
public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class { // FIELDS // узагальнений контекст protected DbContext dbContext; protected DbSet<TEntity> dbSet; // CONSTRUCTORS public GenericRepository(DbContext dbContext) { this.dbContext = dbContext; this.dbSet = dbContext.Set<TEntity>(); } // METHODS public virtual int Count() { return dbSet.Count(); } public virtual int Count(Expression<Func<TEntity, bool>> predicate) { return dbSet.Count(predicate); } public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "", int? page = null, int? amount = null) { // filter IQueryable<TEntity> query = dbSet; if (filter != null) { query = query.Where(filter); } // include properties foreach (string includeProperty in includeProperties.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } // ordering if (orderBy != null) query = orderBy(query); // paging if (page.HasValue && amount.HasValue) query = query.Skip((page.Value - 1) * amount.Value).Take(amount.Value); return query; } public virtual TEntity Get(int id) { return dbSet.Find(id); } public virtual void Insert(TEntity entity) { dbSet.Add(entity); } public virtual void Delete(object id) { // find if (id == null) throw new ArgumentNullException(nameof(id)); TEntity entityToDelete = dbSet.Find(id); // delete finded if (entityToDelete == null) throw new InvalidOperationException("There is no records with such id"); Delete(entityToDelete); } public virtual void Delete(TEntity entityToDelete) { if (entityToDelete == null) throw new ArgumentNullException(nameof(entityToDelete)); if (dbContext.Entry(entityToDelete).State == EntityState.Detached) { dbSet.Attach(entityToDelete); } dbSet.Remove(entityToDelete); } public virtual void Delete(Expression<Func<TEntity, bool>> predicate) { if (predicate != null) dbSet.RemoveRange(dbSet.Where(predicate)); else dbSet.RemoveRange(dbSet); } }
Тепер залишилось для кожної сутності реалізувати свій репозиторій. Напишемо інтерфейс, який додаватиме (а можливо і ні) новий функціонал для конкретного репозиторію.
public interface IUserRepository: IRepository<User> { User GetByName(string name); }
Та конкретна реалізація:
public class UserRepository : GenericRepository<User>, IUserRepository { public UserRepository(DbContext dbContext) : base(dbContext) { } public User GetByName(string name) { return dbSet.First(u => u.Name == name); } }
При потребі варто також узагальнювати тип ключа:
public interface IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey> { . . . }
- Unit Of Work та Repository часто використовують в парі.
![]() | Цей розділ статті ще не написано. (15 червня 2019) |
- Implementing the repository and unit of work patterns [Архівовано 14 вересня 2020 у Wayback Machine.]
- Repository and unit of work pattern [Архівовано 10 серпня 2020 у Wayback Machine.]
- Common mistakes with the repository pattern [Архівовано 13 червня 2019 у Wayback Machine.]