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

时间:2013-12-09 14:40:38

标签: c# .net repository-pattern unit-of-work onion-architecture

这是我第一次实施更多以域驱动的设计方法。我决定尝试Onion Architecture,因为它专注于域而不是基础架构/平台/等。

enter image description here

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

IRepository<T>IUnitOfWork接口:

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

    void Remove(T item);

    IQueryable<T> Query();
}

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

IRepository<T>IUnitOfWork的实体框架实施:

public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> dbSet;

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

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

        dbSet = entityFrameworkUnitOfWork.GetDbSet<T>();
    }

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

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

    public IQueryable<T> Query()
    {
        return dbSet;
    }
}

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>();
    }

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

    public void Dispose()
    {
        context.Dispose();
    }
}

客户存储库:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}

使用存储库的ASP.NET MVC控制器:

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");
    }
}

依赖注入统一:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();

解决方案:

enter image description here

问题吗

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

  • ICustomerRepositoryCustomerRepository类基本上是空的。它们纯粹是为了提供抽象。据我所知,这符合洋葱架构的愿景,其中基础架构和平台相关的代码位于系统外部,但是空类和空接口感觉不对?

  • 要使用不同的持久性实现(比如Azure表存储),则需要创建一个新的CustomerRepository类,并继承AzureTableStorageRepository<T>。但这可能导致冗余代码(多个CustomerRepositories)?这种效果会如何嘲弄?

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

我这样做有什么其他问题吗?

(我从this post获得了大部分灵感)

3 个答案:

答案 0 :(得分:23)

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

让我们来看看其中一些。

<强> 1。依赖注入(DI)和IoC的使用。

您使用最简单版本的Service Locator pattern - container实例本身。

我建议你使用'构造函数注入'。您可以找到更多信息here (ASP.NET MVC 4 Dependency Injection)

public class CustomerController : Controller
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ICustomerRepository customerRepository;

    public CustomerController(
        IUnitOfWork unitOfWork, 
        ICustomerRepository customerRepository)
    {
        this.unitOfWork = unitOfWork;
        this.customerRepository = customerRepository;
    }

    public ActionResult List()
    {
        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        customerRepository.Add(customer);
        unitOfWork.SaveChanges();
        return RedirectToAction("List");
    }
}

<强> 2。工作单位(UoW)范围。

我找不到IUnitOfWorkICustomerRepository的生活方式。我对Unity不熟悉msdn says that TransientLifetimeManager is used by default。这意味着每次解析类型时都会获得一个新实例。

因此,以下测试失败:

[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);
} 

我希望控制器中UnitOfWork的实例与存储库中UnitOfWork的实例不同。有时可能会导致错误。但ASP.NET MVC 4 Dependency Injection中没有突出显示Unity的问题。

Castle Windsor PerWebRequest生活方式用于在单个http请求中共享相同的类型实例。

UnitOfWorkPerWebRequest组件时,这是常见的方法。可以使用自定义ActionFilter来调用Commit()方法期间调用OnActionExecuted()

我还要重命名SaveChanges()方法,并在examplePoEAA中调用它Commit

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

<强> 3.1。对存储库的依赖性。

如果您的存储库将“空”,则不需要为它们创建特定接口。可以解析IRepository<Customer>并在控制器中包含以下代码

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository)
{
    this.unitOfWork = unitOfWork;
    this.customerRepository = customerRepository;
}

有一个测试它的测试。

[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>();
}

但是,如果您希望拥有的存储库是映射层的抽象层,而查询构造代码则集中在该层上。 (PoEAA, Repository

  

存储库在域和数据映射层之间进行调解,   表现得像内存中的域对象集合。客户对象   以声明方式构造查询规范并将其提交给   存储库是为了满足。

<强> 3.2。对EntityFrameworkRepository的继承。

在这种情况下,我会创建一个简单的IRepository

public interface IRepository
{
    void Add(object item);

    void Remove(object item);

    IQueryable<T> Query<T>() where T : class;
}

及其实现知道如何使用EntityFramework基础结构,并且可以轻松替换为另一个(例如AzureTableStorageRepository)。

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();
    }
}

现在CustomerRepository可以成为代理并引用它。

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();
    }
}

答案 1 :(得分:2)

我唯一看到的是你高度依赖你的IOC工具,所以要确保你的实现是可靠的。然而,这并非洋葱设计所独有。我在一些项目中使用了Onion,并没有碰到任何真正的“陷阱”。

答案 2 :(得分:0)

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

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

    var unitOfWork = container.Resolve<IUnitOfWork>();
    var customerRepository = container.Resolve<ICustomerRepository>();

这是隐式依赖。没有UoW,存储库将无法正常工作!并非所有存储库都需要与UoW连接。例如存储过程怎么样?您有存储过程,并将其隐藏在存储库后面。存储过程调用使用单独的事务!至少在所有情况下都不是。因此,如果我解析唯一的存储库并添加项目,那么它将无法工作。此外,如果我设置Transient life license,则此代码将不起作用,因为存储库将具有另一个UoW实例。所以我们有紧密的隐式耦合。

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