4

Dans mon service, j'ai un thread d'arrière-plan qui fait un meilleur effort de sauvegarde d'un flux d'objet de certain type d'entité. Code à peu près est le suivant:DB ConnectionState = Ouvert mais context.SaveChanges génère une exception "connection broken"

 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 
      } 
     } 

(c'est surtout le code actuel, j'omis quelques parties non pertinentes qui tentent de garder des objets éclatés en mémoire jusqu'à ce que nous sommes en mesure de sauver des choses à DB à nouveau lorsque exception est pris à Donc, essentiellement, il y a une boucle sans fin, essayant de lire des éléments de certaines listes et de les enregistrer dans Db. Si nous détectons que la connexion à la base de données a échoué pour une raison quelconque, elle tente simplement de la rouvrir et de continuer. Le problème est que parfois (je ne ai pas comprendre comment le reproduire jusqu'à présent), le code ci-dessus lorsque context.SaveChanges() est appelé commence à produire exception suivante (pris dans (1) bloc):

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. 

L'erreur est enregistrée, mais lorsque l'exécution retourne à la vérification context.GetConnection().State == ConnectionState.Open, elle est évaluée à true. Nous sommes donc dans un état où le contexte signale que sa connexion DB est ouverte, mais nous ne pouvons pas exécuter de requêtes dans ce contexte. Redémarrer le service supprime le problème (ainsi que de jouer avec la variable AllowRun dans le débogueur pour forcer la recréation du contexte). Donc la question est puisque je ne peux pas croire l'état de connexion du contexte, comment puis-je vérifier que je peux exécuter des requêtes contre DB?

De même, y a-t-il un moyen propre de comprendre que la connexion n'est pas dans un état "sain"? Je veux dire, le EntityException en soi n'est pas une indication que je devrais réinitialiser la connexion, seulement si son InnerException est InvalidOperationException avec un message spécifique, alors oui, il est temps de le réinitialiser. Mais, maintenant je suppose qu'il y aurait d'autres situations où ConnectionState indique que tout va bien, mais je ne peux pas interroger DB. Puis-je les prendre de manière proactive, sans attendre que ça commence à me mordre?

+0

Pouvez-vous fournir trace de la pile détaillée? Semble que soit une transaction quelque chose foiré connexion EF en cours ou un problème de délai de connexion expérimenté (éventuellement de MSDTC si les transactions distribuées étant applicables). –

+1

La plupart des parties non pertinentes que vous avez omises sont vraiment pertinentes. Dans l'ensemble, il n'est pas nécessaire de maintenir la connexion pendant la durée de cette opération. La seule façon d'utiliser la connexion est l'appel SaveChanges (par la façon dont vous l'appelez à plusieurs reprises si votre exemple même si pendingLogs est vide). Créez simplement un nouveau contexte lorsque pendingLogs n'est pas vide (la connexion sera renvoyée du pool le plus souvent) et disposez après SaveChanges (la connexion sera renvoyée au pool). Vérifiez également si vos transactions augmentent à distribué et pourquoi (vous appelez peut-être plusieurs bases de données dans un transacton, etc.). – Evk

+1

Pourquoi obtenez-vous un contexte avec une connexion ouverte en premier lieu? –

Répondre

1

Quelle est la fréquence de journal?

si cette boucle prend plus de temps que le délai d'attente de la connexion, la connexion est fermée lorsque le savechanges est en cours d'exécution.

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

"Délai de connexion" est la limite de temps pour * établir * la connexion. Une fois qu'il est établi, il n'y a pas de délai pour le garder ouvert. Ai-je tort? Au moins, je n'avais jamais vu les connexions SQL établies expirer (en dehors des timeouts des requêtes, bien sûr). – n0rd

+0

vous avez raison !! – levent

0

De mon expérience de travail sur des services similaires, la collecte des ordures ne se produira pas jusqu'à la fin du bloc à l'aide .

S'il y avait beaucoup de journaux en attente d'écriture, cela pourrait utiliser beaucoup de mémoire, mais je suppose également qu'il pourrait affamer le pool dbConnection.

Vous pouvez analyser l'utilisation de la mémoire à l'aide ANTS Redgate ou un outil similaire, et vérifier dbConnections qui sont ouverts en utilisant le script suivant de cette question StackOverflow: 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 

;

je pense qu'il est bon de libérer le contexte aussi souvent que vous le pouvez afin de donner GC un changement de nettoyage, de sorte que vous pouvez réécrire la boucle comme:

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 
     } 
    } 
0

Je recommande de passer par Au-dessous de l'url: Expiration du délai expiré est généralement levée lorsqu'une requête sql prend trop de temps à s'exécuter.

Cela ressemble à un travail SQL est en cours d'exécution, de sauvegarde? Cela peut être le verrouillage des tables ou le redémarrage du service.

ADONET async execution - connection broken error