Entity Framework .NET et transactions

.net entity-framework sql-server-2005

Question

En tant que nouvelle personne dans Entity Framework, je suis plutôt bloquée sur la façon de procéder avec cet ensemble de problèmes. Sur le projet sur lequel je travaille actuellement, l'ensemble du site est fortement intégré au modèle EF. Au début, l'accès au contexte EF était contrôlé à l'aide d'un programme d'amorçage Dependency Injection. Pour des raisons opérationnelles, nous n'avons pas pu utiliser de bibliothèque ID. J'ai supprimé ceci et utilisé un modèle d'instances individuelles de l'objet context si nécessaire. J'ai commencé à avoir l'exception suivante:

Le type 'XXX' a été mappé plus d'une fois.

Nous sommes arrivés à la conclusion que les différentes instances du contexte étaient à l'origine de ce problème. J'ai ensuite résumé l'objet de contexte en une seule instance statique à laquelle chaque fil / page accédait. Je reçois maintenant l'une des exceptions concernant les transactions:

La nouvelle transaction n'est pas autorisée car d'autres threads sont en cours d'exécution dans la session.

L'opération de transaction ne peut pas être effectuée car des demandes en attente travaillent sur cette transaction.

ExecuteReader nécessite que la commande ait une transaction lorsque la connexion affectée à la commande se trouve dans une transaction locale en attente. La propriété Transaction de la commande n'a pas été initialisée.

La dernière de ces exceptions s'est produite lors d'une opération de chargement. Je n'essayais pas de sauvegarder l'état de contexte dans la base de données sur le thread qui a échoué. Cependant, un autre thread effectuait une telle opération.

Ces exceptions sont au mieux intermittentes, mais j'ai réussi à faire en sorte que le site aille dans un état où de nouvelles connexions ont été refusées en raison d'un blocage de transaction. Malheureusement, je ne trouve pas les détails de l'exception.

Ma première question est la suivante: le modèle EF doit-il être utilisé à partir d’une instance unique statique? Aussi, est-il possible de supprimer le besoin de transactions en EF? J'ai essayé d'utiliser un objet TransactionScope sans succès ...

Pour être honnête, je suis très coincé ici et je ne comprends pas pourquoi (ce qui devrait être) des opérations assez simples causent un tel problème ...

Réponse acceptée

La création d'un ObjectContext global dans une application Web est très mauvaise. La classe ObjectContext n'est pas thread-safe. Il est construit autour du concept d' unité de travail , ce qui signifie que vous l'utilisez pour gérer un cas d'utilisation unique, donc pour une transaction commerciale. Il est conçu pour traiter une seule demande.

L'exception que vous obtenez se produit car, pour chaque demande, vous créez une nouvelle transaction, mais essayez d'utiliser le même ObjectContext . Vous avez de la chance que ObjectContext détecte cela et lève une exception, car vous venez de découvrir que cela ne fonctionnera pas.

S'il vous plaît pensez à cela pourquoi cela ne peut pas fonctionner. ObjectContext contient un cache local d'entités dans votre base de données. Il vous permet d’apporter de nombreuses modifications et de soumettre ces modifications à la base de données. Lorsque vous utilisez un seul ObjectContext statique, avec plusieurs utilisateurs appelant SaveChanges sur cet objet, comment peut-il savoir exactement ce qui doit être validé et ce qui ne devrait pas? Comme il ne le sait pas, toutes les modifications seront enregistrées, mais à ce stade, un autre utilisateur est peut-être toujours en train de le faire. Si vous avez de la chance, EF ou votre base de données échouera, car les entités sont dans un état non valide. Si vous êtes malchanceux, les objets dont l'état est invalide sont correctement enregistrés dans la base de données et vous pourrez découvrir des semaines plus tard que votre base de données est pleine de merde. La solution à votre problème consiste à créer au moins un ObjectContext par requête . Tandis qu'en théorie, vous pouvez mettre en cache un contexte d'objet dans la session utilisateur, c'est également une mauvaise idée, car ObjectContext vivra généralement trop longtemps et contiendra des données obsolètes (car son cache interne ne sera pas automatiquement actualisé).

MISE À JOUR :

Notez également qu'avoir un ObjectContext par thread est aussi mauvais que d'avoir une seule instance pour l'application Web complète. ASP.NET utilise un pool de threads, ce qui signifie qu'un nombre limité de threads seront créés pendant la durée de vie d'une application Web. Cela signifie en gros que ces instances ObjectContext vivront encore pendant toute la durée de vie de l'application, ce qui entraînerait les mêmes problèmes de staleness des données.

Vous pourriez penser qu'avoir un seul DbContext par thread est réellement thread-safe, mais ce n'est généralement pas le cas, car ASP.NET a un modèle asynchrone qui permet de terminer les requêtes sur un thread différent de celui où il a été démarré MVC et Web API autorisent même un nombre arbitraire de threads à traiter une seule requête dans un ordre séquentiel). Cela signifie que le thread qui a démarré une demande et créé le ObjectContext peut devenir disponible pour traiter une autre demande bien avant la fin de la demande initiale. Toutefois, les objets utilisés dans cette requête (tels qu'une page Web, un contrôleur ou une classe métier) peuvent toujours faire référence à cet ObjectContext . Dans la mesure où la nouvelle requête Web s'exécute dans le même thread, elle obtiendra la même instance ObjectContext que celle ObjectContext par l'ancienne requête. Cela provoque à nouveau des conditions de ObjectContext dans votre application et provoque les mêmes problèmes de sécurité des threads que ce qu'une instance globale ObjectContext provoque.


Réponse populaire

Lorsque vous vous référez au "site" de votre question, je suppose que ceci est une application Web. Les membres statiques n'existent qu'une seule fois pour l'ensemble de l'application. Si vous utilisez un modèle de type singleton avec une seule instance de contexte sur l'ensemble de l'application, toutes sortes de demandes seront dans toutes sortes d'états sur l'ensemble de l'application.

Une seule instance de contexte statique ne fonctionnera pas, mais plusieurs instances de contexte par thread seront gênantes, de même que vous ne pourrez pas mélanger et faire correspondre les contextes. Ce dont vous avez besoin est un contexte unique par thread. Nous l'avons fait dans notre application en utilisant un modèle de type injection de dépendance. Nos classes BLL et DAL prennent un contexte en tant que paramètre dans les méthodes, de cette façon, vous pouvez faire quelque chose comme ci-dessous:

using (TransactionScope ts = new TransactionScope())
{
    using (ObjectContext oContext = new ObjectContext("MyConnection"))
    {
        oBLLClass.Update(oEntity, oContext);
    }
}

Si vous devez appeler d'autres méthodes BLL / DAL dans votre mise à jour (ou quelle que soit la méthode que vous avez choisie), vous passez simplement le même contexte. De cette façon, les mises à jour / insertions / suppressions sont atomiques, tout au sein d'une même méthode utilisant la même instance de contexte, mais cette instance n'est pas utilisée par d'autres threads.



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow