C# 洋葱架构、工作单元和通用存储库模式

C# 洋葱架构、工作单元和通用存储库模式,c#,.net,repository-pattern,unit-of-work,onion-architecture,C#,.net,Repository Pattern,Unit Of Work,Onion Architecture,这是我第一次实现更领域驱动的设计方法。我决定试试,因为它关注的是领域,而不是基础设施/平台等 为了从实体框架中抽象出来,我创建了一个具有工作单元实现的通用存储库 IRepository和IUnitOfWork接口: public interface IRepository<T> { void Add(T item); void Remove(T item); IQueryable<T> Query(); } public interface

这是我第一次实现更领域驱动的设计方法。我决定试试,因为它关注的是领域,而不是基础设施/平台等

为了从实体框架中抽象出来,我创建了一个具有工作单元实现的通用存储库

IRepository
IUnitOfWork
接口:

public interface IRepository<T>
{
    void Add(T item);

    void Remove(T item);

    IQueryable<T> Query();
}

public interface IUnitOfWork : IDisposable
{
    void SaveChanges();
}
客户存储库:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}
public class CustomerController : Controller
{
    UnityContainer container = new UnityContainer();

    public ActionResult List()
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();

        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();; 

        customerRepository.Add(customer);

        unitOfWork.SaveChanges();

        return RedirectToAction("List");
    }
}
公共接口ICCustomerRepository:IRepository
{
}
公共类CustomerRepository:EntityFrameworkRepository,ICCustomerRepository
{
公共CustomerRepository(IUnitOfWork unitOfWork):基础(unitOfWork)
{
}
}
使用存储库的ASP.NET MVC控制器:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}
public class CustomerController : Controller
{
    UnityContainer container = new UnityContainer();

    public ActionResult List()
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();

        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();; 

        customerRepository.Add(customer);

        unitOfWork.SaveChanges();

        return RedirectToAction("List");
    }
}
公共类CustomerController:控制器
{
UnityContainer容器=新的UnityContainer();
公共行动结果列表()
{
var unitOfWork=container.Resolve();
var customerRepository=container.Resolve();
返回视图(customerRepository.Query());
}
[HttpPost]
公共行动结果创建(客户)
{
var unitOfWork=container.Resolve();
var customerRepository=container.Resolve();;
customerRepository.Add(客户);
unitOfWork.SaveChanges();
返回重定向到操作(“列表”);
}
}
unity的依赖注入:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();
container.RegisterType();
container.RegisterType();
解决方案:

问题?

  • 存储库实现(EF代码)非常通用。它都位于
    EntityFrameworkRepository
    类的旁边。具体模型存储库不包含任何这种逻辑。这样可以避免编写大量冗余代码,但可能会牺牲灵活性

  • icCustomerRepository
    CustomerRepository
    类基本上是空的。它们纯粹是为了提供抽象。据我所知,这符合洋葱架构(Onion Architecture)的设想,在洋葱架构中,基础设施和平台相关的代码位于系统的外部,但空类和空接口感觉不对

  • 要使用不同的持久性实现(比如Azure表存储),则需要创建一个新的
    CustomerRepository
    类,并继承一个
    AzureTablesToragerRepository
    。但这可能会导致冗余代码(多个CustomerRepository)?这会对嘲笑产生什么影响

  • 另一个实现(比如Azure表存储)对跨国支持有限制,因此AzureTableStorage UnitOfWork类在这种上下文中不起作用

我这样做还有其他问题吗?


(我的大部分灵感都来自于此)

我看到的唯一一个缺点是,您高度依赖您的IOC工具,因此请确保您的实施是可靠的。然而,这并不是洋葱设计所独有的。我在许多项目中使用过洋葱,但没有遇到任何真正的“问题”。

我可以说,这段代码在第一次尝试时就已经足够好了,但它确实有一些地方需要改进

让我们看看其中的一些

1.依赖注入(DI)和IoC的使用。

您使用最简单版本的-
容器
实例本身

我建议您使用“构造函数注入”。您可以找到更多信息

2.工作单元(UoW)范围。

我找不到
IUnitOfWork
ICustomerRepository
的生活方式。我不熟悉Unity,但它意味着每次解析类型时都会得到一个新实例

因此,以下测试失败:

[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
    target.RegisterType<ICustomerRepository, CustomerRepository>();

    //act
    var unitOfWork1 = target.Resolve<IUnitOfWork>();
    var unitOfWork2 = target.Resolve<IUnitOfWork>();

    // assert
    // This Assert fails!
    unitOfWork1.Should().Be(unitOfWork2);
} 
3.1.对存储库的依赖关系。

如果存储库将为“空”,则无需为其创建特定接口。可以解析
IRepository
,并在控制器中包含以下代码

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository)
{
    this.unitOfWork = unitOfWork;
    this.customerRepository = customerRepository;
}
它的实现知道如何使用EntityFramework基础设施,并且可以很容易地被另一个基础设施取代(例如,
AzureTableStorageRepository


我在代码中看到了几个严重的问题

第一个问题是存储库和UoW之间的关系

    var unitOfWork = container.Resolve<IUnitOfWork>();
    var customerRepository = container.Resolve<ICustomerRepository>();
var unitOfWork=container.Resolve();
var customerRepository=container.Resolve();
这是隐式依赖关系。没有UoW,存储库将无法自行工作!并非所有存储库都需要与UoW连接。例如,存储过程如何?您有存储过程,并将其隐藏在存储库后面。存储过程调用使用单独的事务!至少不是在所有情况下。因此,如果我解析唯一的存储库y和add item,则它将不起作用。此外,如果我设置Transient life许可证,此代码将不起作用,因为存储库将有另一个UoW实例。所以我们有紧密的隐式耦合


第二个问题是在DI容器引擎之间创建紧密耦合,并将其用作服务定位器!服务定位器不是实现IoC和聚合的好方法。在某些情况下,它是反模式的。应该使用DI容器

IoC对洋葱的工作方式不是至关重要的吗?我可以在调用代码中声明具体的类,但恩,我将大大增加耦合并降低可测试性?谢谢你的回答:)是的,这就是为什么我说它的实现需要是可靠的。你需要使用DI和IOC来让洋葱工作。你使用的是依赖于IOC容器。相反,你应该注册一个工厂类来注入你的控制器。SomIoC容器可以以
Func
dependency的形式为您生成其中的一个。谢谢您的回答。(1)完成(2)我一直在努力解决这个问题。现在使用PerResolveLifetimeMana
[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IRepository<Customer>, CustomerRepository>();

    //act
    var repository = target.Resolve<IRepository<Customer>>();

    // assert
    repository.Should().NotBeNull();
    repository.Should().BeOfType<CustomerRepository>();
}
public interface IRepository
{
    void Add(object item);

    void Remove(object item);

    IQueryable<T> Query<T>() where T : class;
}
public class EntityFrameworkRepository : IRepository
{
    public readonly EntityFrameworkUnitOfWork unitOfWork;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        if (entityFrameworkUnitOfWork == null)
        {
            throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
        }

        this.unitOfWork = entityFrameworkUnitOfWork;
    }

    public void Add(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Add(item);
    }

    public void Remove(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Remove(item);
    }

    public IQueryable<T> Query<T>() where T : class
    {
        return unitOfWork.GetDbSet<T>();
    }
}

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new CustomerContext();
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

    internal DbSet GetDbSet(Type type)
    {
        return context.Set(type);
    }

    public void Commit()
    {
        context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}
public interface IRepository<T> where T : class
{
    void Add(T item);

    void Remove(T item);
}

public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
    protected readonly IRepository Repository;

    protected RepositoryBase(IRepository repository)
    {
        Repository = repository;
    }

    public void Add(T item)
    {
        Repository.Add(item);
    }

    public void Remove(T item)
    {
        Repository.Remove(item);
    }
}

public interface ICustomerRepository : IRepository<Customer>
{
    IList<Customer> All();

    IList<Customer> FindByCriteria(Func<Customer, bool> criteria);
}

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
    public CustomerRepository(IRepository repository)
        : base(repository)
    { }

    public IList<Customer> All()
    {
        return Repository.Query<Customer>().ToList();
    }

    public IList<Customer> FindByCriteria(Func<Customer, bool> criteria)
    {
        return Repository.Query<Customer>().Where(criteria).ToList();
    }
}
    var unitOfWork = container.Resolve<IUnitOfWork>();
    var customerRepository = container.Resolve<ICustomerRepository>();