0%

【EntityFramework】DBContext Dispose與否,與DB的連線數是否有關係

案情簡介:

手邊有一個中大型專案,最近Web版上線後,時不時會收到資料庫連線異常相關錯誤,不定時不定量,沒有特定會掛在哪支API,完全沒有邏輯可以歸納。

該Database Layer的ORM使用Entity Framwork(後面簡稱 EF)做為與DB溝通的橋樑,DBContext採用Singleton的方式,每個Request進來,不管跟DB溝通幾次都只會建造一次Context實體,Request結束後Dispose掉。

心中的想像:
以前寫ADO.NET自己控制連線,都聽前輩說一定要自己Close連線,不然很可能會把連線的Pool占滿,所以寫EF時習慣也都會加上Using

1
2
3
4
5
6
using (var DbContext = new NorthwindEntities())
{
//Do Something

}

而且自己覺得每次Dispose應該都會關閉該次連線,註1每次New Context都會建立新的連線,一切井然有序好不快樂,直到這個短時間會有大量的Request湧進來的服務上線後,一切都變調,時不時就會接到使用單位來詢問API不定時中斷問題,想破頭還是找不到問題在哪….

驗證:
**
**從外天空找到行天宮,最後發現最可能的原因是出在EF底層的運作,根據許多篇國外的文章指出,EF是自己控制連線,並不會依照你Dispose或是New Instance就關閉跟建立新連線,所以我特地寫一個範例立馬來實驗一下

首先寫一個ADO.NET版本,並且刻意不要寫SqlConnection . Close(),觀察SQL Connection Pool的狀況

1
2
3
4
5
6
7
8
9
10
11
12
void Main()
{
for (int i = 0; i < 10; i++)
{
SqlConnection conn = new SqlConnection(this.Connection.ConnectionString);
conn.Open();
SqlCommand command = new SqlCommand("select top 1 CustomerID from Customers", conn);
var Result = command.ExecuteScalar().ToString();
Result.Dump();
}
}

資料庫那邊用以下語法查詢目前連線DB得使用者與狀態

1
2
select * from sys.sysprocesses where status = 'sleeping' and spid > 50 order by login_time desc

實驗結果發現Pool立馬從4個暴增到14個

[![](https://1.bp.blogspot.com/-oEdNrW43zrg/WFj8I-X22EI/AAAAAAAAICY/hGYoeCGf7F0wxC4D-pLB5-ILQmOBQ7ASgCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://1.bp.blogspot.com/-oEdNrW43zrg/WFj8I-X22EI/AAAAAAAAICY/hGYoeCGf7F0wxC4D-pLB5-ILQmOBQ7ASgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)
[![](https://3.bp.blogspot.com/-4UVQhQQHP_I/WFj74WVDqsI/AAAAAAAAICU/N8MWSU17kiIm9CoqwaOoJYGorDof8BBHQCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-4UVQhQQHP_I/WFj74WVDqsI/AAAAAAAAICU/N8MWSU17kiIm9CoqwaOoJYGorDof8BBHQCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

接著寫測是第二個版本,有呼叫Close()看看結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Main()
{

for (int i = 0; i < 10; i++)
{
SqlConnection conn = new SqlConnection(this.Connection.ConnectionString);
conn.Open();
SqlCommand command = new SqlCommand("select top 1 CustomerID from Customers", conn);
var Result = command.ExecuteScalar().ToString();
Result.Dump();
conn.Close(); //有呼叫Close
}
}

Pool裡面的閒置Connection數明顯下降,所以有沒有呼叫Close是有差別的,如果有Close的話,會有放掉Connection進而重用閒置的連線

[![](https://3.bp.blogspot.com/-n5iNtVFfa1A/WFj8v__0zPI/AAAAAAAAICg/pv_Yn0Yh3REK0G3quPsd2Q_TwuOOLmNqACLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-n5iNtVFfa1A/WFj8v__0zPI/AAAAAAAAICg/pv_Yn0Yh3REK0G3quPsd2Q_TwuOOLmNqACLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

接著來看看EF的版本,首先測試沒有呼叫Dispose(),且每次Create新的DBContext。(我是用LinqPad測試,所以建立DBContext的地方語法有點不一樣,但不影響結果)

1
2
3
4
5
6
7
8
9
void Main()
{
for (int i = 0; i < 10; i++)
{
var context = new TypedDataContext();
context.Customers.FirstOrDefault().CustomerID.Dump();
}
}

結果看起來連線數也是會自己重用

[![](https://1.bp.blogspot.com/-9dcCNkudzVU/WFj-FcO1zEI/AAAAAAAAICw/u6OqldT3IboAR-5UjsFll0DK4bPdF0FwQCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://1.bp.blogspot.com/-9dcCNkudzVU/WFj-FcO1zEI/AAAAAAAAICw/u6OqldT3IboAR-5UjsFll0DK4bPdF0FwQCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

既然EF會自己控制連線,那是否用Using來Dispose與否也沒差,連線並不會無限制增加?
所以我把程式改成這樣再跑一次

1
2
3
4
5
6
7
8
9
10
11
void Main()
{
for (int i = 0; i < 10; i++)
{
using (var context = new TypedDataContext())
{
context.Customers.FirstOrDefault().CustomerID.Dump();
}
}
}

[![](https://3.bp.blogspot.com/-ueHmNj18HlA/WFj-9VzgvXI/AAAAAAAAIC4/W9-U8j0R3IEaHodcAc1lSR3WkA34LstAgCLcB/s640/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)](https://3.bp.blogspot.com/-ueHmNj18HlA/WFj-9VzgvXI/AAAAAAAAIC4/W9-U8j0R3IEaHodcAc1lSR3WkA34LstAgCLcB/s1600/%25E6%259C%25AA%25E5%2591%25BD%25E5%2590%258D.png)

結果顯示,一模一樣的情況,所以EF的確有自己在控制網路連線這一環,與你Dispose無關,也驗證了這篇文章所說的「 Do I always have to call Dispose() on my DbContext objects? Nope

**
**解決方案 : 

依照上面的時間結果觀察,EF的確有自己在控制連線狀態這件事情,而他什麼時候呼叫Close我並不知道,且還套用了Singleton Pattern,這樣交互影響下不確定是不是導致上述發生的結果,所以我把EF整個拿掉,整個Database Layer用Dapper翻寫,每次連線都自己控制,不再使用EF,上線後至今已經一個禮拜,不再出現任何資料庫連線錯誤。

雖然不敢保證中間推斷的是否百分之百正確,因為如果要更確定,應該要追到EF的底層Code來看他究竟是怎麼寫的,或許觀念大致相同,但是可能會跟我論述的有落差,但的確可以把問題收斂到是EF這個區塊的問題,因為改用Dapper就沒事了。

這個問題整整困擾了將近一年的時間,我想在徹底研究出EF該怎麼解決這個問題之前,大型服務我應該都會暫時放棄這條路了,雖然EF真的很方便阿!!!(吶喊~)

註1:
依照DB Connection Pool的概念,如果有呼叫SqlConnection Close的話,會將Connection釋放到Pool,等到下次有連線時,會優先檢查Pool是否有閒置的可以使用,沒有才會建立新的。
參考: MSDN - SqlConnection.Close 方法 ()

參考文章

1. [在 SQL Server 發現大量在 Sleeping 的連線](https://blogs.msdn.microsoft.com/jchiou/2012/12/10/sql-server-sleeping/)