2013-12-09 48 views
18

这是我第一次实施更多领域驱动的设计方法。我决定尝试Onion Architecture,因为它专注于域名而不是基础设施/平台等。洋葱架构,工作单元和通用存储库模式

enter image description here

为了抽象的实体框架的时候,我已经创建了一个通用仓库与工作实施的单位。

IRepository<T>IUnitOfWork接口:

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

    void Remove(T item); 

    IQueryable<T> Query(); 
} 

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

实体框架的实现和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>(); 

解决方案:

enter image description here

问题?

  • 存储库实现(EF代码)是非常通用的。这一切都坐在EntityFrameworkRepository<T>班。具体模型存储库不包含任何这种逻辑。这使我无法编写大量冗余代码,但可能会牺牲灵活性?

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

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

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

这样做有什么其他问题吗?

(我已经采取了我的大部分灵感来自this post

+2

通过对IoC容器进行依赖性,您正在使用[service locator anti-pattern](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)。相反,你应该注册一个工厂类来注入你的控制器。一些IoC容器可以以'Func '依赖关系 – AlexFoxGill

回答

22

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

让我们通过他们中的一些

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)范围。

我找不到IUnitOfWorkICustomerRepository的生活方式。我不熟悉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 WindsorPerWebRequest生活方式用于共享单个HTTP请求内类型的相同的实例。

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

我也会重命名SaveChanges()方法,并简称为Commit,因为它在examplePoEAA中被调用。

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

谢谢你的出色答案。 (1)完成(2)我正在为这个问题而苦苦挣扎。现在使用PerResolveLifetimeManager(3.1)我现在使用特定的接口用于特定的获取方法(如GetCustomerByName)。我决定从存储库中删除IQueryable Query()方法,因为它将EF细节泄漏到客户端代码中。 (3.2)我会仔细研究一下。 – davenewza

2

我看到的唯一的con是,你是高度依赖于你的IOC的工具,所以一定要确保你的实现是固体。但是,这不是洋葱设计所特有的。我用洋葱上一批项目,并没有遇到任何真正的“陷阱”。

+0

的形式为您生成其中的一种.IooC是如何工作的关键吗?我可以在我的调用代码中声明具体的类,但是接下来我会增加耦合度,并相当大幅地降低可测试性?感谢您的回复:) – davenewza

+1

是的,这就是为什么我说它的实施需要坚实。你需要使用DI和IOC来使洋葱工作。 – Maess

0

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

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

var unitOfWork = container.Resolve<IUnitOfWork>(); 
    var customerRepository = container.Resolve<ICustomerRepository>(); 

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

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

+0

对不起。快速键入并发布错误。 DI容器应该在顶层使用。您必须实施控制器工厂并通过构造器实现依赖注入。使用这种方式,你将消除对容器的冗余依赖,并将获得在ctor中设置的显式依赖。 –

相关问题