基於上篇文章 "《HiBlogs》重寫筆記[1] 從DbContext到依賴註入再到自動註入" 園友 @Flaming丶淡藍@ 吳瑞祥 提出了討論和質疑,嚇得我連夜查詢資料(玩笑~)。 本來重點是想分析“自動註入”和對“註入”有更深的理解。不過既然有疑問和討論那也是很好的。總比時不時來篇“這個不行” ...
基於上篇文章《HiBlogs》重寫筆記[1]--從DbContext到依賴註入再到自動註入園友 @Flaming丶淡藍@ 吳瑞祥 提出了討論和質疑,嚇得我連夜查詢資料(玩笑~)。
本來重點是想分析“自動註入”和對“註入”有更深的理解。不過既然有疑問和討論那也是很好的。總比時不時來篇“這個不行”“那個要死了”的好。
之所以沒有在評論區馬上回覆,是因為我確實不懂。所以下班後趕緊查閱相關資料。
我個人得出來的結論是:DbContext可以單次請求內唯一,且可以不主動釋放。(其實當時心裡也納悶了。asp.net core就是這麼乾的啊,如果有問題還玩個毛線啊)
相關資料:http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext/
這篇資料博客應該還是有一定的權威性的,內容是EF團隊解釋回應。
Hello Jon,
The default behavior of DbContext is that the underlying connection is automatically opened any time is needed and closed when it is no longer needed. E.g. when you execute a query and iterate over query results using “foreach”, the call to IEnumerable<T>.GetEnumerator() will cause the connection to be opened, and when later there are no more results available, “foreach” will take care of calling Dispose on the enumerator, which will close the connection. In a similar way, a call to DbContext.SaveChanges() will open the connection before sending changes to the database and will close it before returning.
Given this default behavior, in many real-world cases it is harmless to leave the context without disposing it and just rely on garbage collection.
That said, there are two main reason our sample code tends to always use “using” or dispose the context in some other way:
1. The default automatic open/close behavior is relatively easy to override: you can assume control of when the connection is opened and closed by manually opening the connection. Once you start doing this in some part of your code, then forgetting to dipose the context becomes harmful, because you might be leaking open connections.
2. DbContext implements IDiposable following the recommended pattern, which includes exposing a virtual protected Dispose method that derived types can override if for example the need to aggregate other unmanaged resources into the lifetime of the context.
By the way, with DbContext the pattern to open the connection manually and override the automatic open/close behavior is a bit awkward:
((IObjectContextAdapter)dbContext).ObjectContext.Connection.Open()
But we have a bug to make this easier as it used to be with ObjectContext before, e.g.:
dbContext.Database.Connection.Open()
Hope this helps,
Diego
谷歌翻譯如下(英文不行,不知道翻譯是否正確):
喬恩,
DbContext的預設行為是底層連接在需要時自動打開,併在不再需要時關閉。例如,當您執行查詢並使用“foreach”迭代查詢結果時,對IEnumerable <T> .GetEnumerator()的調用將導致打開連接,並且稍後再沒有可用的結果,“foreach”將會關閉調用Dispose在枚舉器上,這將關閉連接。以類似的方式,調用DbContext.SaveChanges()將在將更改發送到資料庫之前打開連接,併在返回之前關閉它。
鑒於這種預設行為,在許多現實世界的情況下,離開上下文而不處理它,只依靠垃圾回收是無害的。
也就是說,我們的示例代碼往往總是使用“使用”或以其他方式處理上下文的兩個主要原因:
1.預設的自動打開/關閉行為相對容易被覆蓋:您可以通過手動打開連接來控制何時打開和關閉連接。一旦您在代碼的某些部分開始執行此操作,那麼忘記使用上下文會變得有害,因為您可能會泄露打開的連接。
2.DbContext根據推薦的模式實現IDiposable,其中包括暴露一個虛擬保護的Dispose方法,如果需要將其他非托管資源聚合到上下文的生命周期中,派生類型可以覆蓋。
順便說一下,用DbContext打開手動連接的模式,覆蓋自動打開/關閉的行為有點尷尬:
((IObjectContextAdapter)的DbContext).ObjectContext.Connection.Open()
但是,我們有一個錯誤,使之更容易,因為它曾經與ObjectContext之前,例如:
dbContext.Database.Connection.Open()
希望這可以幫助,
迭戈
光說不練假把式,我們還是親自來測試一下吧。
我們測試分兩種情況:
- 1、主動釋放DbContext
- 2、不釋放DbContext
- 3、最好能用多線程模擬下併發
- 4、然後查看執行時資料庫的連接數,和程式執行完之後資料庫的連接數。
測試代碼:
//模擬資料庫的一些操作(為了相對真實,包含了新增、修改和查詢)
private static void DbOperation(BloggingContext db)
{
db.Blogs.Add(new Blog()
{
Rating = 1,
Url = "www.i.haojima.net"
});
db.SaveChanges();
db.Blogs.First().Url = "www.haojima.net";
db.SaveChanges();
foreach (var item in db.Blogs.Take(10).ToList())
{
Console.WriteLine("查詢到的博客id:" + item.BlogId);
}
}
條件輸入:
static void Main(string[] args)
{
Console.WriteLine("是否主動釋放DbContext(y/n)");
var yes = Console.ReadLine();
Console.WriteLine("請輸入模擬併發量");
var number = Console.ReadLine();
SemaphoreSlim _sem = new SemaphoreSlim(int.Parse(number));
迴圈代碼:
var i = 0;
while (i <= 5000)
{
Console.WriteLine("啟動第" + i++ + "個線程");
_sem.Wait();
#region Thread
new Thread(() =>
{
if (yes == "y")
{
using (BloggingContext bloggingContext = new BloggingContext())//主動釋放
{
DbOperation(bloggingContext);
}
}
else
{
BloggingContext bloggingContext = new BloggingContext();//不釋放
DbOperation(bloggingContext);
}
}).Start();
#endregion
_sem.Release();
查看連接數量(sql語句):
SELECT count(1) AS '連接到EFCoreDemoDB2資料庫的數量' FROM
[Master].[dbo].[SYSPROCESSES] WHERE [DBID] IN ( SELECT
[DBID]
FROM
[Master].[dbo].[SYSDATABASES]
WHERE
NAME='EFCoreDemoDB2'
)
操作截圖如下(你也可以下載demo代碼自行測試):
主動釋放、模擬200併發量
資料庫看到的連接數最多的時候54個
不釋放、模擬200併發量
資料庫看到的連接數最多的時候56個
程式執行完成後,連接自動釋放了
【技巧】:
我們使用ef或dbcontext的時候主要註意三個問題:
- 1、多個線程不能訪問同一個dbcontext
- 2、同一個跟蹤實體不能被多個dbcontext操作
- 3、如果查詢數據不需要被修改,一定按需查詢.select(t=>new Dto(){ })。最不濟也要AsNoTracking().ToList()。
一般也就不會出現奇怪的問題了。
【註意】運行測試的時候用命令行執行或者“開始執行不調試”
demo:https://github.com/zhaopeiym/BlogDemoCode/tree/master/EFCoreDemo
當然,我也不知道這種測試是否合理。如果園友有更好的測試方式可以提供。歡迎大家交流。