2016-08-04 53 views
6

我有一個使用多個數據庫分片的API應用程序,使用StructureMap進行依賴注入。每個API調用中所需的標題之一是ShardKey,它告訴我此調用正在尋址哪個數據庫。爲了實現這一點,我有一個OwinMiddleware類稱爲ShardingMiddleware,其中包含下面的代碼(剪斷爲清楚起見):StructureMap中的跨線程衝突

var nestedContainer = container.GetNestedContainer(); 
using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey 
{ 
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
    await Next.Invoke(context); 
} 

這精美的作品在我的測試環境,並通過集成測試的電池。

但是集成測試實際上是單線程的。當我將這部署到一個QA環境中時,一個真正的應用程序通過多個同時調用在我的API中觸發,事情開始變成梨形。 Ferinstance:

System.ObjectDisposedException:無法訪問處置的對象。造成這種錯誤的一個常見原因是處理從依賴注入解決的上下文,然後嘗試在應用程序的其他地方使用相同的上下文實例。這可能發生在你正在上下文中調用Dispose()或將上下文包裝在using語句中。如果您正在使用依賴注入,則應該讓依賴注入容器負責處理上下文實例。

或其他異常指示StructureMap沒有可用的MyDbContext的有效實例。

對我來說,似乎多線程在某種程度上搞亂了彼此的配置,但對於我的生活我無法理解,因爲我使用嵌套容器來存儲每個API的數據庫上下文呼叫。

任何想法可能會在這裏出錯?

更新:我也嘗試將我的Db上下文抽象爲接口。沒有真正的區別;我仍然收到錯誤

System.InvalidOperationException:嘗試創建類型爲'SomeController'的控制器時發生錯誤。確保控制器有一個無參數的公共構造函數。 ---> StructureMap.StructureMapConfigurationException:沒有默認實例被註冊,並且不能爲類型「MyNamespace.IMyDbContext」

更新2自動確定:我解決了這個問題,但獎金仍然是開放的。請參閱下面的答案。

+0

您的'DbContext'可能會作爲[Captive Dependency]保留下來(http://blog.ploeh.dk/2014/06/02/captive-dependency/)。確保此依賴項的使用者的生命週期的壽命不超過'DbContext'的壽命,或者 - 甚至更好 - 防止將DbContext直接注入到消費者中。 'DbContext'是運行時數據,運行時數據[不應該注入到組件中](https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99)。而是在抽象背後隱藏DbContext。 – Steven

回答

2

嗯...我解決了這個問題,但我不明白爲什麼這有所作爲。

它歸結爲與我最初發布的內容有些細微的差異,我之所以選擇這個內容,是因爲我認爲細節是無足輕重的,會讓問題分心。事實上,我的容器並不是本地定義的;而這是我中間件的保護特性(它繼承了集成測試):

protected IContainer Container { get; private set; } 

然後是Invoke()方法內初始化呼叫:

Container = context.GetNestedContainer(); // gets the nested container created by a previous middleware class, using the context.Environment dictionary 

使用記錄語句整個方法,我下到下面的代碼(如在問題中提到,伐木加):

_logger.Debug($"Line 1 Context={context.GetHashCode}, Container={Container.GetHashCode()}"); 
var db = MyDbContext.ForShard(shardKey.Value); // no need for "using", since DI will automatically dispose 
_logger.Debug($"Line 2 Context={context.GetHashCode}, Container={Container.GetHashCode()}"); 
Container.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
await Next.Invoke(context); 

而且令人歎爲觀止,這裏就是冒出來的日誌:

行1個上下文= 56852305,容器= 48376271

1行上下文= 88275661,容器= 85736099

行2上下文= 56852305,容器= 85736099

Line 2上下文= 88275661,Container = 85736099

令人驚歎!我的中間件的Container屬性被神奇地取代了!儘管事實上它被定義爲private set,並且無論如何,爲了安全起見,我通過MyDbContext.ForShard()的代碼進行了檢查,但沒有發現任何可能導致Container的參考文件混亂的內容。

那麼解決方案是什麼?我在初始化之後聲明瞭一個局部變量container,並用它來代替。

它現在可以工作,但我不明白爲什麼或如何做出改變。

賞金去的人可以解釋這一點。

+0

我認爲,原因與'爲什麼使用HttpContext或ThreadLocal範圍界定嵌套容器?'相同。這裏http://structuremap.github.io/the-container/nested-containers/ – ATechieThought

+0

@ATechieThought你能解釋一下嗎?我總是使用OwinContext來獲取容器,而不是使用HttpContext或ThreadLocal範圍。 –

+0

我的不好。我錯過了。但我的想法是以某種方式通過查看上下文值來參與httpcontext。現在,httpcontext不在圖片中,有沒有可能涉及到MyDbContext,它應該被設置爲singleton?對不起,如果我不以任何方式推理問題。 – ATechieThought

2

您應該重寫此:

using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey 
{ 
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
    await Next.Invoke(context); 
} 

原因using在使用結束處置您的DbContext。

而應該註冊工廠:

var dbFactory =()=>MyDbContext.ForShard(shardKey); 
nestedContainer.Configure(cfg => cfg.For<Func<MyDbContext>>().Use(dbFactory)); 
await Next.Invoke(context); 

,並注入本功能代替的DbContext實例。

+0

如果你注入一個Func而不是一個實例,它會在注入MyDbContext的每個類中打開一個新的Db連接嗎? –

+0

它取決於。默認情況下 - 是的。這是個問題嗎? –

+0

不確定是否有問題。我目前的想法是,單個數據庫連接應該足夠用於一個工作單元。我不確定是否可能會導致衝突,如果有更多的連接 - 或者如果它可能會過度的壓力數據庫有太多的連接打開......但這是我擔心。基本上,如果我想每個UOW有一個連接,我將傳遞該實例,如果我想爲每個存儲庫創建一個連接,我將使用Func。謝謝! –

0

從我看到的日誌是第二請求/線程覆蓋容器和第一個如此恭敬的數據庫上下文都使用相同的連接:

Line 2 Context=56852305, Container=85736099 

應該

Line 2 Context=56852305, Container=48376271 

還是我錯了,所以我不認爲你解決了它。該System.ObjectDisposedException 錯誤是,你用它來創建你的數據庫方面的實例 using 條款,因爲它的Next 代表和 context 設置。我也沒明白行

Container = context.GetNestedContainer(); 

也許你記住

Container = container.GetNestedContainer(); 

了?我不熟悉StructureMap,但我認爲代碼看起來應該是這樣的

var nestedContainer = Container.GetNestedContainer(c => 
        { 
         var db = MyDbContext.ForShard(shardKey); 
         c.For<MyDbContext>().Use(db); 
        }); 

await Next.Invoke(context); 

假設容器關閉並配置db連接。