洋葱架构,工作单位和通用库模式 [英] Onion Architecture, Unit of Work and a generic Repository pattern

查看:198
本文介绍了洋葱架构,工作单位和通用库模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我第一次实施更为领域驱动设计方法。我决定尝试洋葱架构的,因为它的重点领域,而不是基础设施/平台上的/ etc。

This is the first time I am implementing a more domain-driven design approach. I have decided to try the Onion Architecture as it focuses on the domain rather than on infrastructure/platforms/etc.

为了抽象远离实体框架,我创建了一个通用信息库与工作的单位的实施。

In order to abstract away from Entity Framework, I have created a generic repository with a Unit of Work implementation.

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&LT; T&GT; 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控制器:

ASP.NET MVC controller using the repository:

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

依赖注入与统一:

Dependency injection with unity:

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

解决方法:

问题?

  • 仓库实现(EF code)是非常通用的。这一切都坐落在侧 EntityFrameworkRepository&LT; T&GT; 类。具体的模型库不包含任何这种逻辑。这节省了我的写作吨冗余code,但可能牺牲灵活性?

  • Repository implementation (EF code) is very generic. It all sits in side the EntityFrameworkRepository<T> class. Concrete model repositories do not contain any of this logic. This saves me from writing tons of redundant code, but possibly sacrifices flexibility?

ICustomerRepository CustomerRepository 类基本上都是空的。他们是纯粹的存在提供抽象。据我了解,这吻合了洋葱架构,其中,基础设施和平台相关的code坐在你的系统外的视野,但其空的类和空的接口,感觉错了吗?

The ICustomerRepository and CustomerRepository classes are basically empty. They are purely there to provide abstraction. As far as I understand, this fits with the vision of the Onion Architecture, where infrastructure and platform-dependent code sits on the outside of your system, but having empty classes and empty interfaces feels wrong?

要使用不同的持久性实现(说的Azure表存储),然后一个新的 CustomerRepository 类将需要创建并会继承 AzureTableStorageRepository&LT; T&GT; 。但是,这可能会导致冗余code(多CustomerRepositories)?这将如何影响嘲讽?

To use a different persistence implementation (say Azure Table Storage), then a new CustomerRepository class would need to be created and would inherit a AzureTableStorageRepository<T>. But this could lead to redundant code (multiple CustomerRepositories)? How would this effect mocking?

另一种实现(说的Azure表存储)对跨国支持限制所以一AzureTableStorageUnitOfWork类也不会在这方面的工作。

Another implementation (say Azure Table Storage) has limitations on transnational support so the a AzureTableStorageUnitOfWork class wouldn't work in this context.

是否还有其他问题,我这样做的方法是什么?

(我已经采取了我的大部分灵感来自<一个href="http://$c$creview.stackexchange.com/questions/19037/entity-framework-generic-repository-pattern">this帖子)

(I have taken most of my inspiration from this post)

推荐答案

我可以说,这code是首次尝试不够好,但它确实有一些地方要改进。

I can say that this code is good enough for the first time try but it does have some places to improve.

让我们通过其中的一些。

Let's go through some of them.

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

您使用 Service Locator模式的简单版本 - 容器实例本身。

You use the simplest version of Service Locator pattern - container instance itself.

我建议你使用的构造函数注射。你可以在这里找到更多的信息(ASP.NET MVC 4依赖注入)

I suggest you use 'constructor injection'. You can find more information 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)范围单元。

我找不到生活方式 IUnitOfWork ICustomerRepository 。我不熟悉的团结,但 MSDN说,TransientLifetimeManager默认情况下使用。这意味着你每次当你解决型时间得到一个新的实例。

I can't find lifestyle of IUnitOfWork and ICustomerRepository. I am not familiar with Unity but msdn says that TransientLifetimeManager is used by default. It means that you'll get a new instance every time when you resolve type.

所以,下面的测试失败:

So, the following test fails:

[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依赖注入突出作为一个问题的统一。

And I expect that instance of UnitOfWork in your controller differs from the instance of UnitOfWork in your repository. Sometimes it may be resulted in bugs. But it is not highlighted in the ASP.NET MVC 4 Dependency Injection as an issue for Unity.

温莎城堡 PerWebRequest 生活方式用于共享在一个HTTP请求类型相同的实例。

In Castle Windsor PerWebRequest lifestyle is used to share the same instance of type within single http request.

这是当的UnitOfWork 是一个 PerWebRequest 组件。自 ActionFilter 可以以调用提交()使用 OnActionExecuted的调用过程中() 方法。

It is common approach when UnitOfWork is a PerWebRequest component. Custom ActionFilter can be used in order to invoke Commit() during invocation of OnActionExecuted() method.

我还要重命名的SaveChanges()的方法,简单地命名为提交因为它是所谓的<一个href="http://$c$creview.stackexchange.com/questions/19037/entity-framework-generic-repository-pattern">example而在 POEAA 的。

I would also rename the SaveChanges() method and call it simply Commit as it is called in the example and in the PoEAA.

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

3.1。依赖于资源库。

如果你的仓库都将是空来创建特定的接口,对于他们来说是不需要的。它可以解决 IRepository&LT;客户&GT; 并具有以下code在控制器

If your repositories are going to be 'empty' it is not needed to create specific interfaces for them. It is possible to resolve IRepository<Customer> and have the following code in your controller

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

有一个测试,测试它。

There is a test that tests it.

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

不过,如果你想拥有仓库是抽象在映射层,其中查询构造code富集层。 ( POEAA,库

一个仓库领域和数据映射层之间的中间,   像个内存中的域对象集合。客户对象   构建查询规范声明,并提交给   存储库的满意度。

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction.

3.2。传承上EntityFrameworkRepository。

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

In this case I would create a simple 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 可以是一个代理,并引用它。

And now CustomerRepository can be a proxy and refer to it.

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

这篇关于洋葱架构,工作单位和通用库模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆