IMORepository
抽象和UnitOfWork
抽象在任何有意义的开发中都具有非常有价值的位置。人们会争论实现细节,但正如给猫剥皮的方法有很多一样,实现抽象的方法也有很多。
您的问题是具体使用或不使用以及为什么。
毫无疑问,您已经意识到您已经将这两种模式都内置到了实体框架中,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 上看到这个问题——“如何让实体框架在多租户环境中工作?”。
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
如果您有Repository
抽象,那么答案是“添加装饰器很容易”</p>
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 您应该始终对将在多个地方引用的任何第 3 方组件进行简单的抽象。从这个角度来看,ORM 是完美的候选者,因为它在我们的很多代码中都被引用。
当有人说“我为什么要对这个或那个 3rd 方库进行抽象(例如)”时,通常会想到的答案Repository
是“你为什么不呢?”</p>
使用 IoC 容器(例如SimpleInjector )应用 PS 装饰器非常简单。
[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>)));
}
}
}