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

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

在此处输入图像描述

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

IRepositoryIUnitOfWork接口:

 public interface IRepository { void Add(T item); void Remove(T item); IQueryable Query(); } public interface IUnitOfWork : IDisposable { void SaveChanges(); } 

IRepositoryIUnitOfWorkentity framework实现:

 public class EntityFrameworkRepository : IRepository where T : class { private readonly DbSet dbSet; public EntityFrameworkRepository(IUnitOfWork unitOfWork) { var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork; if (entityFrameworkUnitOfWork == null) { throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork"); } dbSet = entityFrameworkUnitOfWork.GetDbSet(); } public void Add(T item) { dbSet.Add(item); } public void Remove(T item) { dbSet.Remove(item); } public IQueryable Query() { return dbSet; } } public class EntityFrameworkUnitOfWork : IUnitOfWork { private readonly DbContext context; public EntityFrameworkUnitOfWork() { this.context = new CustomerContext();; } internal DbSet GetDbSet() where T : class { return context.Set(); } public void SaveChanges() { context.SaveChanges(); } public void Dispose() { context.Dispose(); } } 

客户存储库:

 public interface ICustomerRepository : IRepository { } public class CustomerRepository : EntityFrameworkRepository, 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(); var customerRepository = container.Resolve(); return View(customerRepository.Query()); } [HttpPost] public ActionResult Create(Customer customer) { var unitOfWork = container.Resolve(); var customerRepository = container.Resolve();; customerRepository.Add(customer); unitOfWork.SaveChanges(); return RedirectToAction("List"); } } 

统一dependency injection:

 container.RegisterType(); container.RegisterType(); 

解:

在此处输入图像描述

问题?

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

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

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

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

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

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

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

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

1.dependency injection(DI)和IoC的使用。

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

我建议你使用’构造函数注入’。 您可以在此处找到更多信息(ASP.NET MVC 4dependency 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说默认使用TransientLifetimeManager 。 这意味着每次解析类型时都会获得一个新实例。

因此,以下测试失败:

 [Test] public void MyTest() { var target = new UnityContainer(); target.RegisterType(); target.RegisterType(); //act var unitOfWork1 = target.Resolve(); var unitOfWork2 = target.Resolve(); // assert // This Assert fails! unitOfWork1.Should().Be(unitOfWork2); } 

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

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

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

我还将重命名SaveChanges()方法,并在示例和PoEAA中调用它,就像调用它一样 。

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

3.1。 对存储库的依赖性。

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

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

有一个测试它的测试。

 [Test] public void MyTest() { var target = new UnityContainer(); target.RegisterType, CustomerRepository>(); //act var repository = target.Resolve>(); // assert repository.Should().NotBeNull(); repository.Should().BeOfType(); } 

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

存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合。 客户端对象以声明方式构造查询规范,并将其提交到存储库以获得满意。

3.2。 EntityFrameworkRepository的inheritance。

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

 public interface IRepository { void Add(object item); void Remove(object item); IQueryable Query() 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 Query() where T : class { return unitOfWork.GetDbSet(); } } public interface IUnitOfWork : IDisposable { void Commit(); } public class EntityFrameworkUnitOfWork : IUnitOfWork { private readonly DbContext context; public EntityFrameworkUnitOfWork() { this.context = new CustomerContext(); } internal DbSet GetDbSet() where T : class { return context.Set(); } internal DbSet GetDbSet(Type type) { return context.Set(type); } public void Commit() { context.SaveChanges(); } public void Dispose() { context.Dispose(); } } 

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

 public interface IRepository where T : class { void Add(T item); void Remove(T item); } public abstract class RepositoryBase : IRepository 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 { IList All(); IList FindByCriteria(Func criteria); } public class CustomerRepository : RepositoryBase, ICustomerRepository { public CustomerRepository(IRepository repository) : base(repository) { } public IList All() { return Repository.Query().ToList(); } public IList FindByCriteria(Func criteria) { return Repository.Query().Where(criteria).ToList(); } } 

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

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

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

  var unitOfWork = container.Resolve(); var customerRepository = container.Resolve(); 

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

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