關於DbContext能不能單次請求內唯一?DbContex需不需要主動釋放?歡迎各路大俠來“參戰”!


基於上篇文章《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

當然,我也不知道這種測試是否合理。如果園友有更好的測試方式可以提供。歡迎大家交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM