0%

【Entity Framework】The connection was not closed. The connection's current state is connecting

#前情提要

不定時、不定量、不特定API跳出**【System.InvalidOperationException】 - The connection was not closed. The connection’s current state is connecting.**錯誤,這個錯誤以前公司也遇到過,當時研判是Entity Framework與Unity套件生命週期的問題,當時專案不算太大又一直找不到確切解決方案,所以索性將Repository Layer整個用Dapper改寫讓問題得以解決了。

但這次退無可退,再次印證了一句話

你不解決問題,問題就會解決你

   

#分析

透過Elmah的Log分析,發現共同點都壞在一支Token驗證的DelegatingHandler裡面,因為呼叫每支API都會經過這,可以解釋為何壞在不特定API。

而關鍵在其中的一行

1
2
var Service = AutofacConfig.Container.Resolve<ISupplierApiProfileService>();
var Profile = Service.GetSupplierApiProfileByToken(token);

透過Autofac Container取得ISupplierApiProfileService,猜測當時這樣撰寫是希望每次產生新的Service的實體來使用,在這之前先一下看一下整體的架構

/images/20180724/1.gif

而每個實體分別Autofac註冊的生命週期為

/images/20180724/2.jpg

這導致了一個結果,因為Actofac Container為Singleton的實體,透過他Resolve出來的DBContext (InstancePerLifetimeScope)也會變成Singleton的生命週期。

而Entity Framework非Thread Safe的設計,所以如果不同Thread共用同一個DBContext時,如果前一個交易尚未完成,撞在一起時就會有機率產生這個錯誤

/images/20180724/3.jpg

#解法

而DBContext會設定為InstancePerLifetimeScope的生命週期是希望DBContext隨著每次Request產生與消滅(亦即每個Request只會產生一次DBContext實體),如果後續應用Transaction才不會發生問題,但在DelegatingHandler這邊的應用情境卻導致了Singleton的情況。

BeginLifetimeScope

Autofac提供給使用者產生一個動態的生命週期的方法,所有透過這個生命週期產生的實體會隨著它的消滅一併被Disposed掉,使用方式如下

1
2
3
4
5
using (var scope = AutofacConfig.Container.BeginLifetimeScope())
{
var Service = scope.Resolve<ISupplierApiProfileService>();
var Profile = Service.GetSupplierApiProfileByToken(token);
}

這樣可以確保在DelegatingHandler裡面產生的DBContext不會變成Singleton,也不用動到最基底DBContext InstancePerLifetimeScope的生命週期,確保了每次Request共用同一個DBContext。

上線後觀察Elmah,確認這個伴隨著我將近三年的問題終於徹底消滅了,可喜可賀 可喜可賀。