4

在我的服務中,我有一個後臺線程,盡力保存特定實體類型的對象流。代碼大致就是下面的:DB ConnectionState =打開但context.SaveChanges拋出「連接中斷」異常

 while (AllowRun) 
     { 
      try 
      { 
       using (DbContext context = GetNewDbContext()) 
       { 
        while (AllowRun && context.GetConnection().State == ConnectionState.Open) 
        { 
         TEntity entity = null; 
         try 
         { 
          while (pendingLogs.Count > 0) 
          { 
           lock (pendingLogs) 
           { 
            entity = null; 
            if (pendingLogs.Count > 0) 
            { 
             entity = pendingLogs[0]; 
             pendingLogs.RemoveAt(0); 
            } 
           } 

           if (entity != null) 
           { 
            context.Entities.Add(entity); 
           } 
          } 
          context.SaveChanges(); 
         } 
         catch (Exception e) 
         { 
          // (1) 
          // Log exception and continue execution 
         } 
        } 
       } 
      } 
      catch (Exception e) 
      { 
       // Log context initialization failure and continue execution 
      } 
     } 

(這主要是實際的代碼,我省略了幾個試圖保持在內存中彈出的對象,直到我們能夠的東西保存到數據庫時再次異常在被捕獲的非相關部分(1) block)

所以,本質上,有一個無限循環,試圖從某個列表讀取項目並將其保存到Db。如果我們檢測到與數據庫的連接由於某種原因而失敗,它只是試圖重新打開它並繼續。問題是,有時(我無法弄清楚如何重現它至今),當context.SaveChanges()被稱爲上面的代碼開始產生以下異常(陷入(1)塊):

System.Data.EntityException: An error occurred while starting a transaction on the provider connection. See the inner exception for details. ---> 
System.InvalidOperationException: The requested operation cannot be completed because the connection has been broken. 

的誤差記錄,但是當執行返回到context.GetConnection().State == ConnectionState.Open檢查時,它的計算結果爲true。所以我們處於上下文報告其數據庫連接已打開的狀態,但我們無法針對該上下文運行查詢。重新啓動該服務將消除該問題(以及在調試器中調用AllowRun變量以強制重新創建上下文)。所以問題是因爲我不能信任上下文的連接狀態,我如何驗證我可以對數據庫運行查詢?

另外,是否有一個乾淨的方法來弄清楚連接不處於「健康」狀態?我的意思是,EntityException本身不是一個跡象表明我應該重置連接,只有當它的InnerException是InvalidOperationException與某些特定的消息,那麼是的,現在是時候重置它。但是,現在我猜想在ConnectionState指示一切正常時會出現其他情況,但我無法查詢數據庫。我可以主動地抓住那些人,而不是等到它開始咬我嗎?

+0

你能提供詳細的堆棧跟蹤?似乎是事務搞砸了正在進行的EF連接或遇到連接超時問題(如果分佈式事務正在應用,可能來自MSDTC)。 –

+1

您最有可能忽略的非相關部分確實相關。總而言之,在此操作的整個生命週期內不需要保持連接。您使用連接的唯一方法是SaveChanges調用(通過您重複調用它的方式,如果您的示例即使pendingLogs爲空)。只要在pendingLogs不爲空時創建新的上下文(連接將最常從池中返回)並在SaveChanges後處理(連接將返回到池)。同時檢查您的交易是否升級爲分佈式,以及爲什麼(也許您在一個交易中調用多個數據庫等)。 – Evk

+1

爲什麼你首先得到一個開放連接的上下文? –

回答

1

什麼是日誌頻率?

如果此循環花費比連接超時更長的時間,則在執行savechanges時關閉連接。

while (pendingLogs.Count > 0) 
{ 
    lock (pendingLogs) 
    { 
      entity = null; 
      if (pendingLogs.Count > 0) 
      { 
      entity = pendingLogs[0]; 
      pendingLogs.RemoveAt(0); 
      } 
    } 

    if (entity != null) 
    { 
      context.Entities.Add(entity); 
    } 
} 
context.SaveChanges(); 
+0

「連接超時」是*建立*連接的時間限制。一旦建立,就沒有超時保持打開狀態。我錯了嗎?至少我從來沒有見過建立的SQL連接超時(當然,查詢超時之外)。 – n0rd

+0

你是對的! – levent

0

從我的經驗上類似的服務工作,不會產生垃圾收集,直到使用塊結束。

如果有很多Pending日誌需要寫入,這可能會佔用很多內存,但我也猜測它可能會使dbConnection池不能使用。

可以使用展鵬螞蟻或類似的工具分析內存使用情況,並從這個StackOverflow的問題,檢查dbConnections打開的使用下面的腳本:how to see active SQL Server connections?

SELECT 
    DB_NAME(dbid) as DBName, 
    COUNT(dbid) as NumberOfConnections, 
    loginame as LoginName 
FROM 
    sys.sysprocesses 
WHERE 
    dbid > 0 
GROUP BY 
    dbid, loginame 

;

我認爲這是爲經常可以爲了給GC清理的改變,所以你可以重寫環路釋放的背景下很好的做法:

while (AllowRun) 
    { 
     try 
     { 
      while (pendingLogs.Count > 0) 
      { 
       using (DbContext context = GetNewDbContext()) 
       { 
        while (AllowRun && context.GetConnection().State == ConnectionState.Open) 
        { 
         TEntity entity = null; 
         try 
         { 

          lock (pendingLogs) 
          { 
           entity = null; 
           if (pendingLogs.Count > 0) 
           { 
            entity = pendingLogs[0]; 
            pendingLogs.RemoveAt(0); 
           } 
          } 

          if (entity != null) 
          { 
           context.Entities.Add(entity); 
           context.SaveChanges(); 
          } 
         }       
         catch (Exception e) 
         { 
          // (1) 
          // Log exception and continue execution 
         } 
        } 
       } 
      } 
     } 
     catch (Exception e) 
     { 
      // Log context initialization failure and continue execution 
     } 
    }