【问题标题】:Dependency injection in unit of work pattern using repositories使用存储库的工作单元模式中的依赖注入
【发布时间】:2013-04-17 16:03:16
【问题描述】:

我想创建一个工作单元类,它以类似于this 的方式环绕存储库。

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

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

这是强耦合的。

我可以看到两种解决方法。

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

1 的问题是,如果我注入所有存储库,我必须实例化每个存储库,即使我不在那个特定的工作单元实例中使用它们。因此会产生这样做的开销。我设想使用一个数据库范围的工作单元类,因此这会导致大量不必要的实例化和巨大的构造函数。

2 的问题是很容易忘记设置并以空引用异常结束。

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

我刚刚开始依赖注入,并且已经完成了我能找到的关于该主题的所有研究,但我可能遗漏了一些关键。

【问题讨论】:

  • Lazy&lt;T&gt; 与构造函数注入一起使用会有什么问题,所以只有在您实际使用它时才会加载它?我知道并不是所有的 IoC 容器都支持 Lazy 开箱即用。
  • 这实际上有什么好处吗?我会用一个对象的创建来代替另一个对象的创建。我怀疑存储库(其构造函数仅分配上下文依赖项)在创建时会产生很大的开销。
  • 并不是说这是要走的路,但您说构造函数注入的问题是会产生实例化每个存储库的开销。 Lazy 的创建实际上没什么。

标签: c# entity-framework dependency-injection unit-of-work


【解决方案1】:

解决这个问题的方法是不让UnitOfWork 负责通过容器注入创建每个Repository,而是让每个Repository 负责确保UnitOfWork 知道它的存在在实例化时。

这将确保

  • 您的UnitOfWork 不需要为每个新的Repository 更改
  • 您没有使用服务定位器(许多人认为是anti-pattern

最好用一些代码来演示——我使用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);
    }
}

每个真实的Repository 都继承自GenericRepository

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,并且您已在编译时验证您的所有存储库都已定义。你的代码是open for extension but closed for modification

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

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 的活动实例准备就绪并等待 Submit() 对数据库进行任何更改。

你可以装饰你的 UnitOfWork 来处理事务等。我会听从强大的 .NetJunkie 并推荐你阅读这两篇文章 herehere

【讨论】:

  • 您可以向容器注册上下文,并将其注入到UnitOfWorkRepository 的对象构造函数中。将Context 注册到上面显示的lifetimeScope 将确保向所有对象注入相同的实例。
  • 现在你这么说似乎很明显,但我不知道你能做到这一点。是的,这会很好用。
  • 很好的答案,谢谢!这帮助我解决了我在解决方案中遇到的最后一个问题。
  • 这是一个优雅的解决方案,适用于整个存储库/UOW 场景 - 非常全面的代码示例和很好的解释。关于答案末尾链接的 .net 瘾君子文章 - 我现在在生产解决方案中使用了这种方法,并且效果非常好。
  • @Kleky,您可以枚举字典。它不需要是字典:HashSet&lt;&gt; 也一样好。
【解决方案2】:

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

【讨论】:

  • 你能举个例子吗?
【解决方案3】:

我的解决方案是 UnitOfWork 仍然负责创建存储库,但我在 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>();
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 2023-03-23
    • 2021-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多