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

Dav*_*New 19 .net c# unit-of-work repository-pattern onion-architecture

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

在此输入图像描述

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

IRepository<T>IUnitOfWork接口:

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

    void Remove(T item);

    IQueryable<T> Query();
}

public interface IUnitOfWork : IDisposable
{
    void SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

实体框架的实现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();
    }
}
Run Code Online (Sandbox Code Playgroud)

客户资料库:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

使用存储库的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");
    }
}
Run Code Online (Sandbox Code Playgroud)

统一依赖注入:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();
Run Code Online (Sandbox Code Playgroud)

解:

在此输入图像描述

问题?

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

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

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

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

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

(我从这篇文章中获取了大部分灵感)

Ily*_*kin 23

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

让我们来看看其中的一些.

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

您使用最简单的Service Locator模式 - container实例本身.

我建议你使用'构造函数注入'.您可以在此处找到更多信息(ASP.NET MVC 4依赖注入).

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

2.工作单位(UoW)范围.

我找不到生活方式IUnitOfWorkICustomerRepository.我不熟悉Unity,但msdn说默认使用TransientLifetimeManager.这意味着每次解析类型时都会获得一个新实例.

因此,以下测试失败:

[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);
} 
Run Code Online (Sandbox Code Playgroud)

我希望UnitOfWork你的控制器中的实例与UnitOfWork你的存储库中的实例不同.有时可能会导致错误.但它并没有在ASP.NET MVC 4依赖注入中突出显示为Unity的一个问题.

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

这是当常用的方法UnitOfWorkPerWebRequest组件.ActionFilter可以使用自定义以Commit()在调用OnActionExecuted()方法期间调用.

我也会重命名该SaveChanges()方法,并Commit示例PoEAA中调用它.

public interface IUnitOfWork : IDisposable
{
    void Commit();
}
Run Code Online (Sandbox Code Playgroud)

3.1.对存储库的依赖性.

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

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository)
{
    this.unitOfWork = unitOfWork;
    this.customerRepository = customerRepository;
}
Run Code Online (Sandbox Code Playgroud)

有一个测试它的测试.

[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>();
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您希望将存储库设置为映射层的抽象层,而查询构造代码则集中在该层上.(PoEAA,存储库)

存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合.客户端对象以声明方式构造查询规范,并将它们提交给Repository以满足要求.

3.2.EntityFrameworkRepository上的继承.

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

public interface IRepository
{
    void Add(object item);

    void Remove(object item);

    IQueryable<T> Query<T>() where T : class;
}
Run Code Online (Sandbox Code Playgroud)

及其实现,知道如何使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在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();
    }
}
Run Code Online (Sandbox Code Playgroud)