Repository — Вікіпедія

UML діаграма, що описує структуру шаблону проєктування Repository

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

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

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

Переваги

[ред. | ред. код]
  • Використовується, як колекція
  • Інкапсулює великі запити до БД в методи
  • Рівень абстракції між Business Logic рівнем та Data Access рівнем
  • Ізолює програму від змін джерела даних
  • Джерело даних може бути змінено без будь-яких змін в бізнес логіці і з мінімальними змінами в Репозиторії
  • Полегшує автоматизоване юніт тестування, Test Driven Development
  • Легко створювати mock репозиторію

Недоліки

[ред. | ред. код]
  • Зростає кількість класів
  • Погіршує продуктивність
  • Обмежує у використанні особливостей ОРМ фреймворку

Опис мовою C#

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

Використаємо 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 часто використовують в парі.

Реалізація

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

Див. також

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

Джерела

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