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
类基本上是空的。它们纯粹是为了提供抽象。据我所知,这符合洋葱架构(Onion Architecture)的设想,在洋葱架构中,基础设施和平台相关的代码位于系统的外部,但空类和空接口感觉不对CustomerRepository
- 要使用不同的持久性实现(比如Azure表存储),则需要创建一个新的
类,并继承一个CustomerRepository
。但这可能会导致冗余代码(多个CustomerRepository)?这会对嘲笑产生什么影响AzureTablesToragerRepository
- 另一个实现(比如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>();