IMO 在任何有意义的开发中,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);
}
}
IMO,您应该始终将一个简单的抽象放在将要在多个地方引用的任何第三方组件上。从这个角度来看,ORM是我们的很多代码中都引用的理想选择。
当有人说“为什么我应该Repository
对此或那个第三方库进行抽象(例如)”时,通常会想到一个答案:“为什么不呢?”
PS装饰器使用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>)));
}
}
}