2013-12-09 46 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中設置的顯式依賴。 –

相關問題