#前情提要
不定時、不定量、不特定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 | var Service = AutofacConfig.Container.Resolve<ISupplierApiProfileService>(); |
透過Autofac Container取得ISupplierApiProfileService,猜測當時這樣撰寫是希望每次產生新的Service的實體來使用,在這之前先一下看一下整體的架構
而每個實體分別Autofac註冊的生命週期為
這導致了一個結果,因為Actofac Container為Singleton的實體,透過他Resolve出來的DBContext (InstancePerLifetimeScope)也會變成Singleton的生命週期。
而Entity Framework非Thread Safe的設計,所以如果不同Thread共用同一個DBContext時,如果前一個交易尚未完成,撞在一起時就會有機率產生這個錯誤
#解法
而DBContext會設定為InstancePerLifetimeScope的生命週期是希望DBContext隨著每次Request產生與消滅(亦即每個Request只會產生一次DBContext實體),如果後續應用Transaction才不會發生問題,但在DelegatingHandler這邊的應用情境卻導致了Singleton的情況。
BeginLifetimeScope
官方文件 - Working with Lifetime Scopes
Autofac提供給使用者產生一個動態的生命週期的方法,所有透過這個生命週期產生的實體會隨著它的消滅一併被Disposed掉,使用方式如下
1 | using (var scope = AutofacConfig.Container.BeginLifetimeScope()) |
這樣可以確保在DelegatingHandler裡面產生的DBContext不會變成Singleton,也不用動到最基底DBContext InstancePerLifetimeScope的生命週期,確保了每次Request共用同一個DBContext。
上線後觀察Elmah,確認這個伴隨著我將近三年的問題終於徹底消滅了,可喜可賀 可喜可賀。