Entity Framework : que faire face à l’erreur «new transaction is not allowed because there are other threads running »

Je travaillais il y a de cela quelques jours sur une application ASP.NET MVC 3, avec l’ORM Entity Framework. J’ai été confronté lors de l’écriture de mon code à l’erreur suivante : «new transaction is not allowed because there are other threads running ».

Comme il n’est pas exclu que je tombe encore sur le même type de problème à l’avenir, je vais partager sur mon blog – qui est avant tout mon rappel mémoire favori – les causes de cette erreur et comment contourner cela.

Ce que j’essayais de faire dans mon application.

Pour une facture, je voulais récupérer la liste des produits, et pour chaque produit, avoir la fiche produit correspondante, y effectuer des modifications, ensuite appliquer la mise à jour à la base de données.

Le code source de cette partie est le suivant :

 

var lp = from l in db.Liste_produit.Include(l => l.Article)
where l.Id_facture == facture.Id_facture
select l;
foreach(Liste_produit liste_produit in lp)
{
article = liste_produit.Article;
//bla bla bla
//bla bla bla
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();

}

À l’exécution de ce code, au moment de l’enregistrement des modifications dans la base de données (db.SaveChanges()), l’exception ci-dessous est levée.

Les causes :

Après avoir observé le problème de près et effectué quelques recherches, je me suis rendu compte que le souci était au niveau du foreach.

Il semblerait que pour la lecture de chaque élément dans « lp », la bouche ait recours à une transaction pour le Context en cours. Hor, lorsque j’appelle la procédure SaveChanges(), une nouvelle transaction veut être créée pour le même Context. Ce qui n’est pas autorisé par Entity Famework.

Solutions

De prime abord, j’ai opté pour la sauvegarde de mes données dans une IList. Ainsi, lors de la lecture de celles-ci, je n’aurais plus besoin d’avoir recours à une transaction.
Le code que j’ai utilisé est le suivant :

var lp = from l in db.Liste_produit.Include(l => l.Article)
where l.Id_facture == fa.Id_facture
select l;
IList<Liste_produit> ListeP = lp.ToList();
foreach(Liste_produit liste_produit in ListeP)
{
article = liste_produit.Article;
//bla bla bla
//bla bla bla
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();

}

Cette solution marche. Mais, côté performance, elle n’est pas du tout satisfaisante. Si la quantité de données est importante, imaginez un peu la consommation de la mémoire en chargeant toutes ces données dans une liste.

En tout cas, elle ne m’arrange pas. J’ai donc opté pour une solution beaucoup plus simple et performante. J’ai simplement appelé la procédure SaveChanges() en dehors de la boucle et le problème a été résolu avec de meilleures performances.

var lp = from l in db.Liste_produit.Include(l => l.Article)
where l.Id_facture == facture.Id_facture
select l;
foreach(Liste_produit liste_produit in lp)
{
article = liste_produit.Article;
//bla bla bla
//bla bla bla
db.Entry(article).State = EntityState.Modified;
}
db.SaveChanges();

En procédant ainsi, j’apporte des modifications à l’entité dans le foreach, et c’est à la suite de cette instruction (lorsque la transaction a été bouclée) que j’appelle la procédure SaveChanges().

Voila le problème est résolu, et la prochaine fois que je serais face à ce problème (ce qui est fort probable), et que je ne me souviendrais plus de comment j’avais procédé (ce qui est également fort probable), je saurais ou chercher e premier ;).

Laisser un commentaire