ІМО, і Repository
абстракція, і UnitOfWork
абстракція займають дуже цінне місце в будь-якому значущому розвитку. Люди будуть сперечатися щодо деталей реалізації, але так само, як існує безліч способів зняти шкіру з кота, існує багато способів здійснити абстракцію.
Ваше питання конкретно використовувати чи не використовувати і чому.
Оскільки ви, без сумніву, зрозуміли, що ці два шаблони вже вбудовані в Entity Framework, DbContext
є UnitOfWork
і DbSet
є Repository
. Як правило, вам не потрібно юніт-тестувати себе UnitOfWork
або Repository
себе, оскільки вони просто сприяють між вашими класами та базовими реалізаціями доступу до даних. Що вам доведеться робити знову і знову, це глузування з цих двох абстракцій під час модульного тестування логіки ваших послуг.
Ви можете знущатись, підробляти чи щось інше за допомогою зовнішніх бібліотек, додаючи рівні залежностей коду (які ви не контролюєте) між логікою, що проводить тестування, і логікою, що перевіряється.
Отже, другорядним моментом є те , що наявність власної абстракції UnitOfWork
та Repository
дає вам максимальний контроль та гнучкість при знущанні над вашими модульними тестами.
Все дуже добре, але для мене справжня сила цих абстракцій полягає в тому, що вони забезпечують простий спосіб застосування методів орієнтованого на аспекти та дотримання принципів SOLID .
Отже, у вас є IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
І його реалізація:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
Поки що нічого незвичайного, але зараз ми хочемо додати кілька протоколів - легко з реєстрацією Декоратора .
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
Все зроблено і без змін до нашого існуючого коду . Існує безліч інших наскрізних проблем, які ми можемо додати, такі як обробка винятків, кешування даних, перевірка даних чи що завгодно, і в процесі проектування та побудови найцінніше, що є у нас, що дозволяє нам додавати прості функції, не змінюючи жодного з існуючих кодів. це наша IRepository
абстракція .
Зараз я багато разів бачив це запитання на StackOverflow - "як зробити так, щоб Entity Framework працював у середовищі з декількома орендарями?".
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Якщо у вас Repository
абстракція, тоді відповідь: "легко додати декоратора"
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
//public for Unit Test example
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
ІМО, ви завжди повинні розміщувати просту абстракцію над будь-яким стороннім компонентом, на який буде посилатися у кількох місцях. З цієї точки зору ORM є ідеальним кандидатом, на що посилається стільки нашого коду.
Відповідь, яка зазвичай спадає на думку, коли хтось каже: «навіщо мені абстракція (наприклад, Repository
) над тією чи іншою бібліотекою третьої сторони", - "чому б вам не?"
PS Decorators надзвичайно просто застосовувати за допомогою контейнера IoC, такого як SimpleInjector .
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}