这是我第一次实施更多以域驱动的设计方法。我决定尝试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();
}
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>();
解决方案:
问题吗
存储库实现(EF代码)非常通用。它都位于EntityFrameworkRepository<T>
类的旁边。具体模型存储库不包含任何此逻辑。这使我免于编写大量冗余代码,但可能会牺牲灵活性?
ICustomerRepository
和CustomerRepository
类基本上是空的。它们纯粹是为了提供抽象。据我所知,这符合洋葱架构的愿景,其中基础架构和平台相关的代码位于系统外部,但是空类和空接口感觉不对?
要使用不同的持久性实现(比如Azure表存储),则需要创建一个新的CustomerRepository
类,并继承AzureTableStorageRepository<T>
。但这可能导致冗余代码(多个CustomerRepositories)?这种效果会如何嘲弄?
另一种实现(比如Azure表存储)对跨国支持有限制,因此AzureTableStorageUnitOfWork类在此上下文中不起作用。
我这样做有什么其他问题吗?
(我从this post获得了大部分灵感)
答案 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)范围。
我找不到IUnitOfWork
和ICustomerRepository
的生活方式。我对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请求中共享相同的类型实例。
当UnitOfWork
是PerWebRequest组件时,这是常见的方法。可以使用自定义ActionFilter
来调用Commit()
方法期间调用OnActionExecuted()
。
我还要重命名SaveChanges()
方法,并在example和PoEAA中调用它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容器