使用仓储库的工作单元模式中的依赖注入

51

我想创建一个工作单元类,以类似于 这个例子中的方式包装存储库。

我遇到的问题是尝试通过将示例中的通用仓储替换为IRepository接口来实现依赖注入。在链接文章中的uow中,他们使用getter来检查存储库是否已实例化,如果没有,则进行实例化。

public GenericRepository<Department> DepartmentRepository
{
    get
    {
        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

这是强耦合的情况。

我看到了两种解决方法。

  1. 使用构造函数注入。
  2. 使用setter注入。

第一种方法的问题在于,如果我注入所有的repositories,即使我在特定的工作单元实例中不使用它们,也必须实例化每个repository,从而产生开销。我原本想使用一个适用于整个数据库的工作单元类,这将导致很多无用的实例化和一个巨大的构造函数。

第二种方式的问题是容易忘记设置并最终出现空引用异常。

这种情况下是否有任何最佳实践?还有其他我可能错过的选项吗?

我刚开始接触依赖注入,并进行了所有我能找到的关于这个主题的研究,但我可能错过了一些关键的东西。


1
使用构造函数注入时,使用 Lazy<T> 会有什么问题呢?这样只有在实际使用时才会加载。我知道并非所有的 IoC 容器都原生支持 Lazy - Nick Freeman
2
这样做实际上有什么好处吗?我将创建一个对象来替换另一个对象的创建。我怀疑当创建一个仓储(其构造函数只分配上下文依赖项)时,开销不会太大。 - rashleighp
不是说这是解决问题的方法,但你说构造函数注入的问题在于实例化每个存储库会产生开销。创建一个Lazy几乎没有任何开销。 - Nick Freeman
3个回答

70
一种解决方法是不让UnitOfWork通过容器注入创建每个Repository,而是让每个Repository在实例化时确保UnitOfWork知道它的存在。
这将确保:
  • 您的UnitOfWork不需要为每个新的Repository进行更改
  • 您没有使用服务定位器(被许多人认为是反模式
最好通过一些代码来演示 - 我使用SimpleInjector,所以示例基于此:
Repository抽象开始:
public interface IRepository 
{
    void Submit();
}
public interface IRepository<T> :IRepository where T : class { }
public abstract class GenericRepository<T> : IRepository<T> where T : class { }

UnitOfWork

public interface IUnitOfWork
{
    void Register(IRepository repository);
    void Commit();
}

每个Repository都必须向UnitOfWork注册自己,这可以通过更改抽象父类GenericRepository来实现:
public abstract class GenericRepository<T> : IRepository<T> where T : class
{
    public GenericRepository(IUnitOfWork unitOfWork)
    {
        unitOfWork.Register(this);
    }
}

每个真实的仓库都继承自通用仓库:
public class Department { }
public class Student { }

public class DepartmentRepository : GenericRepository<Department> 
{
    public DepartmentRepository(IUnitOfWork unitOfWork): base(unitOfWork) { }
}

public class StudentRepository : GenericRepository<Student>
{
    public StudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { }
}

加入实际的实现UnitOfWork,你就可以开始了:

public class UnitOfWork : IUnitOfWork
{
    private readonly Dictionary<string, IRepository> _repositories;
    public UnitOfWork()
    {
        _repositories = new Dictionary<string, IRepository>();
    }

    public void Register(IRepository repository)
    {
        _repositories.Add(repository.GetType().Name, repository);
    }

    public void Commit()
    {
        _repositories.ToList().ForEach(x => x.Value.Submit());
    }
}

容器注册可以设置为自动获取所有定义的IRepository实例,并将它们注册到生命周期范围内,以确保它们在事务的生命周期内都能存活:
public static class BootStrapper
{
    public static void Configure(Container container)
    {
        var lifetimeScope = new LifetimeScopeLifestyle();

        container.Register<IUnitOfWork, UnitOfWork>(lifetimeScope);

        container.RegisterManyForOpenGeneric(
            typeof(IRepository<>),
            lifetimeScope,
            typeof(IRepository<>).Assembly);
    }
}

通过这些抽象和基于DI的架构,您拥有一个UnitOfWork,它知道在任何服务调用中实例化的所有Repository,并且您具有编译时验证,确保所有存储库都已定义。您的代码开放扩展但关闭修改

要测试所有这些内容-添加这些类

public class SomeActivity
{
    public SomeActivity(IRepository<Department> departments) { }
}

public class MainActivity
{
    private readonly IUnitOfWork _unitOfWork;
    public MainActivity(IUnitOfWork unitOfWork, SomeActivity activity) 
    {
        _unitOfWork = unitOfWork;
    }

    public void test()
    {
        _unitOfWork.Commit();
    }
}

将这些代码添加到BootStrapper.Configure()

//register the test classes
container.Register<SomeActivity>();
container.Register<MainActivity>();

对代码行设置断点:

_repositories.ToList().ForEach(x => x.Value.Submit());

最后,运行此控制台测试代码:

class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();
        BootStrapper.Configure(container);
        container.Verify();
        using (container.BeginLifetimeScope())
        {
            MainActivity entryPoint = container.GetInstance<MainActivity>();
            entryPoint.test();
        }
    }
}

你会发现代码在断点处停止,你有一个活动的 IRepository 实例准备好并等待将任何更改提交到数据库。
你可以装饰你的 UnitOfWork 来处理事务等。在这一点上,我会推荐你阅读这两篇文章 herehere,并听从强大的 .NetJunkie 的建议。

3
您可以将上下文注册到容器中,并将其注入到“UnitOfWork”和“Repository”的对象构造函数中。在上面显示的“lifetimeScope”中注册上下文将确保相同的实例被注入到所有对象中。 - qujck
2
这是整个存储库/UOW方案的优雅解决方案——非常全面的代码示例和很好的解释。关于答案末尾链接的 .net 专家文章——我已经在生产中使用此方法,表现非常好。 - Baldy
1
@Kleky,你可以枚举字典。它不一定是一个字典:HashSet<> 也同样好。 - qujck
1
RegisterManyForOpenGeneric 现在更名为 Register,而不是 RegisterCollection。仓储实例只有在对象图中的其他位置引用它们时才会出现在 uow 中。 - qujck
1
喜欢这个答案,谢谢 @qujck 先生! - Jeancarlo Fontalvo
显示剩余29条评论

7

不要注入存储库实例,而是注入单个工厂对象,该对象将负责创建这些实例。 然后,您的getter将使用该工厂。


2
请问您能否提供一个例子? - Krishna
你能展示一个例子吗? - Sangeeth Nandakumar

4

我的解决方案是UnitOfWork仍然负责创建Repository,但我在UnitOfWork中添加了一个GetRepository()工厂方法来完成这个任务。

public interface IUnitOfWork : IDisposable
{
    T GetRepository<T>() where T : class;
    void Save();
}

public class UnitOfWork : IUnitOfWork
{
    private Model1 db;

    public UnitOfWork() :  this(new Model1()) { }

    public UnitOfWork(TSRModel1 dbContext)
    {
        db = dbContext;
    }

    public T GetRepository<T>() where T : class
    {          
        var result = (T)Activator.CreateInstance(typeof(T), db);
        if (result != null)
        {
            return result;
        }
        return null;
    }

    public void Save()
    {
        db.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                db.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class TestRepository : GenericRepository<Test>, ITestRepository
{
    public TestRepository(Model1 db)
       : base(db)
    {
    }
}

public class TestManager: ITestManager
{
    private IUnitOfWork unitOfWork;
    private ITestRepository testRepository;
    public TestManager(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        testRepository = unitOfWork.GetRepository<TestRepository>();
    }

}

感谢@sonmt和[at]qujck,这是一个实现。我有一些空缺需要填补。主要基于[在]sonmt的解决方案。但这将运行(F5)并且是端到端的。希望它能帮助某人!https://github.com/DocGreenRob/TestableGenericRepository - Robert Green MBA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接