3

通過直接在命令 - 和查詢處理程序DbContext依賴,我明白,我從StackOverflow user違反SOLID-principles因爲這樣評論的:使用的DbContext的SOLID方式

DbContext是用布袋請求特定的運行時數據並將運行時數據注入構造函數會導致麻煩。讓你的 代碼直接依賴於DbContext導致你的代碼 違反DIP和ISP,這使得難以維護。

這是完全有道理的,但我不確定如何解決它可能使用IoC和DI?

通過最初雖然是建立與可用於查詢的上下文中的單一方法的IUnitOfWork

public interface IUnitOfWork 
{ 
    IQueryable<T> Set<T>() where T : Entity; 
} 

internal sealed class EntityFrameworkUnitOfWork : IUnitOfWork 
{ 
    private readonly DbContext _context; 

    public EntityFrameworkUnitOfWork(DbContext context) 
    { 
     _context = context; 
    } 

    public IQueryable<T> Set<T>() where T : Entity 
    { 
     return _context.Set<T>(); 
    } 
} 

現在,我可以在我的查詢處理程序取決於IUnitOfWork(接收數據),我有解決了這部分。

接下來,我需要看一下我的命令(修改數據),我可以解決變化的節約與我的命令的裝飾背景:

internal sealed class TransactionCommandHandler<TCommand> : IHandleCommand<TCommand> where TCommand : ICommand 
{ 
    private readonly DbContext _context; 
    private readonly Func<IHandleCommand<TCommand>> _handlerFactory; 

    public TransactionCommandHandler(DbContext context, Func<IHandleCommand<TCommand>> handlerFactory) 
    { 
     _context = context; 
     _handlerFactory = handlerFactory; 
    } 

    public void Handle(TCommand command) 
    { 
     _handlerFactory().Handle(command); 
     _context.SaveChanges(); 
    } 
} 

這工作得很好,以及。

第一個問題是:如何從我的命令處理程序修改上下文中的對象,因爲我不能直接依賴DbContext了?

像:context.Set<TEntity>().Add(entity);

據我瞭解我要創建另一個接口,這與固原則進行工作。例如,一個ICommandEntities將包含像void Create<TEntity>(TEntity entity)這樣的方法,更新,刪除,回滾甚至重新加載。然後依靠我的命令中的這個接口,但是我在這裏錯過了一個觀點,我們是否抽象得太深?

第二個問題是:這是在使用DbContext時尊重SOLID原則的唯一方式,還是這是違反原則的「可以」的地方?

如果需要,我使用Simple Injector作爲我的IoC容器。

+0

您是否真的需要將DbContext抽象爲IUnitOfWork。即您是否可能用另一個ORM替換EF?如果沒有,爲什麼要介紹看似不必要的複雜性? –

+0

您肯定有一個觀點 - 我可以看到這種方法的優點和缺點。 – janhartmann

+1

我使用NHibernate並沒有計劃改變。我直接使用NHibernate Sessions進行事務管理等事情。但是,我傾向於傾向於存儲庫和查詢模式,以便單元測試更容易。 –

回答

3

與您EntityFrameworkUnitOfWork,你還是違反了以下部分:

的的DbContext與請求特定的運行時數據包和運行時數據注入構造引起麻煩

你的對象圖應該是無狀態的,並且狀態應該在運行時通過對象圖。你應該EntityFrameworkUnitOfWork如下所示:

internal sealed class EntityFrameworkUnitOfWork : IUnitOfWork 
{ 
    private readonly Func<DbContext> contextProvider; 

    public EntityFrameworkUnitOfWork(Func<DbContext> contextProvider) 
    { 
     this.contextProvider = contextProvider; 
    } 

    // etc 
} 

有一個抽象與單一IQueryable<T> Set<T>()方法查詢的偉大工程。它使孩子玩遊戲,以後添加基於權限的基於過濾的IQueryable<T>,而不必更改查詢處理程序中的任何代碼行。

請注意,雖然抽象揭示了IQueryable<T>(如此IUnitOfWork)抽象,但仍然違反了SOLID原則。這是因爲IQueryable<T>是一個泄漏抽象,這基本上意味着依賴倒置原則違反。 IQueryable是一個漏洞抽象,因爲在EF上運行的LINQ查詢不會自動在NHibernate上運行,反之亦然。但是至少我們在這種情況下要更加牢靠一點,因爲它防止我們必須通過查詢處理程序進行全面更改,以防需要應用權限過濾或其他類型的過濾。

試圖從您的查詢處理程序中完全抽象出O/RM是沒用的,並且只會導致您將LINQ查詢移動到另一個層,或者會使您恢復到SQL查詢或存儲過程。但是,再次提取O/RM並不是問題,能夠在應用程序的正確位置應用交叉問題是個問題。最後,如果你遷移到NHibernate,你很可能不得不重寫一些你的查詢處理程序。在這種情況下,您的集成測試將直接告訴您哪些處理程序需要更改。

但足夠說關於查詢處理程序;讓我們來談談命令處理程序。他們需要做更多的工作,DbContext。你最終可能會考慮讓命令處理程序直接依賴DbContext。但我仍然希望他們不要這樣做,並讓我的命令處理程序僅依賴於SOLID抽象。這怎麼看起來可能會因應用而變化,但由於命令處理程序通常是真正的重點,並改變短短的實體,我喜歡的東西,如:

interface IRepository<TEntity> { 
    TEntity GetById(Guid id); 
    // Creates an entity that gets saved when the transaction is committed, 
    // optionally using an id supplied by the client. 
    TEntity Create(Guid? id = null); 
} 

在我的工作,我們幾乎沒有刪除任何系統。所以這可以防止我們在IRepository<TEntity>上使用Delete方法。當在該接口上同時使用GetByIdCreate時,更改已經很高,您將違反接口隔離原則,並且非常小心不要添加更多方法。你甚至可能想分割它們。如果你看到你的命令處理程序變得很大,並且有很多依賴關係,你可能需要將它們拆分成Aggregate Services,或者如果結果更糟糕,你可以考慮從你的IUnitOfWork返回庫,但是你必須小心不要丟失增加交叉擔憂的可能性。

這是唯一的辦法用的DbContext

工作時尊重SOLID-原則這絕對不是唯一的方式。我會說最令人愉快的方式是應用領域驅動設計並使用聚合根。在後臺,你可能會有一個O/RM爲你保留一個完整的聚合,完全隱藏在命令處理程序和實體本身之外。如果您可以將此對象圖完全序列化爲JSON並將其作爲blob存儲在您的數據庫中,則更令人愉快。這完全消除了首先需要具有O/RM工具的需求,但這實際上意味着您擁有文檔數據庫。你最好使用一個真正的文檔數據庫,否則幾乎不可能查詢這些數據。

還是這是一個地方,其「違反」的原則違反?

無論你做什麼,你都必須在某處違反SOLID原則。這取決於你違反它們的好處以及堅持使用它們的好處。

+0

謝謝@Steven,我有一種感覺,你會來。一如既往的好回答。感謝您通過注入'DbContext'來指出缺陷。我將在一個可用的'IRepository '抽象中工作,通過讀/寫分割並通過IUnitOfWork保存修飾器來保存更改以用於命令。再次感謝! :-) – janhartmann