SqlException from Entity Framework - Une nouvelle transaction n'est pas autorisée car d'autres threads s'exécutent dans la session


601

Je reçois actuellement cette erreur:

System.Data.SqlClient.SqlException: la nouvelle transaction n'est pas autorisée car d'autres threads s'exécutent dans la session.

lors de l'exécution de ce code:

public class ProductManager : IProductManager
{
    #region Declare Models
    private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
    private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
    #endregion

    public IProduct GetProductById(Guid productId)
    {
        // Do a quick sync of the feeds...
        SyncFeeds();
        ...
        // get a product...
        ...
        return product;
    }

    private void SyncFeeds()
    {
        bool found = false;
        string feedSource = "AUTO";
        switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
        {
            case "AUTO":
                var clientList = from a in _dbFeed.Client.Include("Auto") select a;
                foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
                {
                    var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
                    foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                    {
                        if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                        {
                            var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                            foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                            {
                                foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                                {
                                    if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                    {
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found)
                                {
                                    var newProduct = new RivWorks.Model.Negotiation.Product();
                                    newProduct.alternateProductID = sourceProduct.AutoID;
                                    newProduct.isFromFeed = true;
                                    newProduct.isDeleted = false;
                                    newProduct.SKU = sourceProduct.StockNumber;
                                    company.Product.Add(newProduct);
                                }
                            }
                            _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                        }
                    }
                }
                break;
        }
    }
}

Modèle # 1 - Ce modèle se trouve dans une base de données sur notre serveur de développement. Modèle # 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Modèle n ° 2 - Ce modèle se trouve dans une base de données sur notre serveur Prod et est mis à jour chaque jour par des flux automatiques. texte alternatif http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Remarque - Les éléments entourés de rouge dans le modèle n ° 1 sont les champs que j'utilise pour "mapper" au modèle n ° 2. Veuillez ignorer les cercles rouges dans le modèle n ° 2: cela vient d'une autre question que j'ai eue et à laquelle il est maintenant répondu.

Remarque: J'ai encore besoin de mettre un chèque isDeleted afin que je puisse le supprimer en douceur de DB1 s'il est sorti de l'inventaire de notre client.

Tout ce que je veux faire, avec ce code particulier, c'est connecter une entreprise dans DB1 avec un client dans DB2, obtenir leur liste de produits dans DB2 et l'insérer dans DB1 si elle n'est pas déjà là. La première fois devrait être un tirage complet de l'inventaire. Chaque fois qu'il y est exécuté, rien ne devrait se produire à moins qu'un nouvel inventaire n'entre dans le flux pendant la nuit.

Donc, la grande question - comment résoudre l'erreur de transaction que je reçois? Dois-je supprimer et recréer mon contexte à chaque fois à travers les boucles (cela n'a pas de sens pour moi)?


6
C'est la question la plus détaillée que j'aie jamais vue.

9
Quelqu'un manque encore des procédures stockées?
David

Réponses:


690

Après avoir beaucoup arraché mes cheveux, j'ai découvert que les foreachboucles étaient les coupables. Ce qui doit arriver, c'est d'appeler EF mais de le retourner dans un IList<T>de ce type cible puis de boucler sur le IList<T>.

Exemple:

IList<Client> clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
   var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
    // ...
}

14
Ouais, ça m'a aussi fait mal à la tête. J'ai failli tomber de ma chaise quand j'ai trouvé le problème! Je comprends les raisons techniques du problème, mais ce n'est pas intuitif et cela n'aide pas le développeur à tomber dans le "gouffre du succès" blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Docteur Jones

9
N'est-ce pas mauvais pour les performances des grands ensembles de données? Si vous avez des millions d'enregistrements dans le tableau. ToList () les suce tous en mémoire. Je rencontre ce problème et je me demandais si ce qui suit serait faisable a) Détachez l'entité b) Créez un nouveau ObjectContext et attachez-y l'entité détachée. c) Appelez SaveChanges () sur le nouveau ObjectContext d) Détachez l'entité du nouveau ObjectContext e) Reliez-la à l'ancien ObjectContext
Abhijeet Patel

150
Le problème est que vous ne pouvez pas appeler SaveChangespendant que vous extrayez des résultats de la base de données. Par conséquent, une autre solution consiste simplement à enregistrer les modifications une fois la boucle terminée.
Drew Noakes du

4
Ayant été mordu également, j'ai ajouté ceci à Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/… N'hésitez pas à voter.
Ian Mercer

36
Nos développeurs ont tendance à ajouter .ToList () à n'importe quelle requête LINQ sans penser aux conséquences. Ce doit être la première fois que l'ajout de .ToList () est vraiment utile!
Marc

267

Comme vous l'avez déjà identifié, vous ne pouvez pas enregistrer à partir d'un fichier foreachqui continue de puiser dans la base de données via un lecteur actif.

Appeler ToList()ou ToArray()convient pour les petits ensembles de données, mais lorsque vous avez des milliers de lignes, vous consommerez une grande quantité de mémoire.

Il est préférable de charger les lignes en morceaux.

public static class EntityFrameworkUtil
{
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
    {
        return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
    }

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
    {
        int chunkNumber = 0;
        while (true)
        {
            var query = (chunkNumber == 0)
                ? queryable 
                : queryable.Skip(chunkNumber * chunkSize);
            var chunk = query.Take(chunkSize).ToArray();
            if (chunk.Length == 0)
                yield break;
            yield return chunk;
            chunkNumber++;
        }
    }
}

Compte tenu des méthodes d'extension ci-dessus, vous pouvez écrire votre requête comme suit:

foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
    // do stuff
    context.SaveChanges();
}

L'objet interrogable sur lequel vous appelez cette méthode doit être commandé. En effet, Entity Framework ne prend IQueryable<T>.Skip(int)en charge que les requêtes ordonnées, ce qui est logique lorsque vous considérez que plusieurs requêtes pour des plages différentes nécessitent que l'ordre soit stable. Si l'ordre n'est pas important pour vous, il vous suffit de trier par clé primaire, car il est probable qu'il ait un index clusterisé.

Cette version interrogera la base de données par lots de 100. Notez que cela SaveChanges()est appelé pour chaque entité.

Si vous souhaitez améliorer considérablement votre débit, vous devez appeler SaveChanges()moins fréquemment. Utilisez plutôt un code comme celui-ci:

foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
    foreach (var client in chunk)
    {
        // do stuff
    }
    context.SaveChanges();
}

Cela se traduit par 100 fois moins d'appels de mise à jour de base de données. Bien sûr, chacun de ces appels prend plus de temps à se terminer, mais vous finissez toujours par aller de l'avant. Votre kilométrage peut varier, mais ce fut un monde plus rapide pour moi.

Et cela contourne l'exception que vous voyiez.

EDIT J'ai revu cette question après avoir exécuté SQL Profiler et mis à jour quelques éléments pour améliorer les performances. Pour toute personne intéressée, voici quelques exemples de SQL qui montrent ce qui est créé par la base de données.

La première boucle n'a pas besoin de sauter quoi que ce soit, c'est donc plus simple.

SELECT TOP (100)                     -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

Les appels suivants doivent ignorer les blocs de résultats précédents, donc introduit l'utilisation de row_number:

SELECT TOP (100)                     -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
    SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
    OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
    FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100   -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC

17
Merci. Votre explication était beaucoup plus utile que celle marquée comme "Répondue".
Wagner da Silva

1
C'est bien. juste une chose: si vous interrogez sur une colonne et mettez à jour la valeur de cette colonne, vous devez être le chunkNumber ++; . Disons que vous avez une colonne "ModifiedDate" et que vous interrogez .Where (x => x.ModifiedDate! = Null), et à la fin de foreach vous définissez une valeur pour ModifiedDate. De cette façon, vous n'itérez pas la moitié des enregistrements, car la moitié des enregistrements sont ignorés.
Arvand

Malheureusement, sur d'énormes ensembles de données, vous obtiendrez OutofMemoryException - voir l'explication dans l'ensemble de données Entity Framework, exception de mémoire insuffisante . J'ai décrit comment renouveler votre contexte à chaque lot dans SqlException à partir d'Entity Framework - Une nouvelle transaction n'est pas autorisée car d'autres threads s'exécutent dans la session
Michael Freidgeim

Je pense que cela devrait fonctionner. var skip = 0; const int take = 100; Répertorier <Employee> emps; while ((emps = db.Employees.Skip (skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Faites des trucs ici}} Je formulerais ceci une réponse mais elle serait enterrée sous les piles de réponses ci-dessous et elle se rapporte à cette question.
jwize

124

Nous avons maintenant publié une réponse officielle au bogue ouvert sur Connect . Les solutions de contournement que nous recommandons sont les suivantes:

Cette erreur est due à Entity Framework créant une transaction implicite lors de l'appel SaveChanges (). La meilleure façon de contourner l'erreur consiste à utiliser un modèle différent (c'est-à-dire à ne pas enregistrer en cours de lecture) ou à déclarer explicitement une transaction. Voici trois solutions possibles:

// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
    foreach (var person in context.People)
    {
        // Change to person
    }
    context.SaveChanges();
}

// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
    using (var context = new MyContext())
    {
        foreach (var person in context.People)
        {
            // Change to person
            context.SaveChanges();
        }
    }
    transaction.Complete();
}

// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
    var people = context.People.ToList(); // Note that this forces the database
                                          // to evaluate the query immediately
                                          // and could be very bad for large tables.

    foreach (var person in people)
    {
        // Change to person
        context.SaveChanges();
    }
} 

6
Si vous prenez la route Transaction, le simple fait de lancer un TransactionScope pourrait ne pas le résoudre - n'oubliez pas d'étendre le délai d'attente si ce que vous faites peut prendre du temps - par exemple, si vous déboguez de manière interactive le code faisant le Appel DB. Voici le code étendant le délai d'expiration de la transaction à une heure: using (var transaction = new TransactionScope (TransactionScopeOption.Required, new TimeSpan (1, 0, 0)))
Chris Moschini

J'ai rencontré cette erreur la toute première fois que je me suis éloigné du "chemin du tutoriel" pour en faire un véritable exemple! Pour moi, cependant, la solution la plus simple, ÉCONOMISER APRÈS L'ITÉRATION, mieux c'est! (Je pense que 99% des fois c'est le cas, et seulement 1% DOIT vraiment effectuer une sauvegarde de base de données DANS LA boucle)
spiderman

Brut. Je suis juste tombé sur cette erreur. Très sale. La 2ème suggestion a fonctionné comme un charme pour moi avec le déplacement de mes SaveChanges dans la boucle. Je pensais que la sauvegarde des modifications en dehors de la boucle était meilleure pour les modifications par lots. Mais d'accord. Je suppose que non?! :(
M. Young

N'a pas fonctionné pour moi .NET 4.5. Lors de l'utilisation du TransactionScope, j'ai eu l'erreur suivante "Le fournisseur sous-jacent a échoué sur EnlistTransaction. {" Le gestionnaire de transactions partenaire a désactivé sa prise en charge des transactions à distance / réseau. (Exception de HRESULT: 0x8004D025) "}". Je finis par faire le travail en dehors de l'itération.
Diganta Kumar

L'utilisation de TransactionScope est dangereuse, car la table est verrouillée pendant toute la transaction.
Michael Freidgeim

19

En effet, vous ne pouvez pas enregistrer les modifications dans une foreachboucle en C # en utilisant Entity Framework.

context.SaveChanges() La méthode agit comme une validation sur un système de base de données standard (RDMS).

Effectuez simplement toutes les modifications (qu'Entity Framework mettra en cache), puis enregistrez-les toutes à la fois en appelant SaveChanges()après la boucle (en dehors de celle-ci), comme une commande de validation de base de données.

Cela fonctionne si vous pouvez enregistrer toutes les modifications en même temps.


2
Je pensais qu'il était intéressant de voir "un système de base de données régulier (RDMS)" ici
Dinerdo

1
Cela semble faux, car appeler plusieurs fois SaveChanges est très bien dans 90% des contextes dans EF.
Pxtl

Il semble que l'appel répété de SaveChanges soit correct, sauf si la boucle foreach est en train d'itérer sur une entité db.
kerbasaurus

1
Aha! Mettez le contexte à l'intérieur de chaque boucle! (pffft ... à quoi je pensais? ..) Merci!
Adam Cox

18

Mettez juste context.SaveChanges()après la fin de votre foreach(boucle).


C'est la meilleure option que j'ai trouvée dans mon cas en raison de la sauvegarde à l'intérieur de foreach
Almeida

2
Ce n'est pas toujours une option.
Pxtl

9

Utilisez toujours votre sélection comme liste

Par exemple:

var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();

Parcourez ensuite la collection tout en enregistrant les modifications

 foreach (var item in tempGroupOfFiles)
             {
                 var itemToUpdate = item;
                 if (itemToUpdate != null)
                 {
                     itemToUpdate.FileStatusID = 8;
                     itemToUpdate.LastModifiedDate = DateTime.Now;
                 }
                 Entities.SaveChanges();

             }

1
Ce n'est pas du tout une bonne pratique. Vous ne devriez pas exécuter SaveChanges aussi souvent si vous n'en avez pas besoin, et vous ne devriez certainement pas "Toujours utiliser votre sélection comme liste"
Dinerdo

@Dinerdo cela dépend vraiment du scénario. Dans mon cas, j'ai 2 boucles foreach. L'extérieur avait la requête db comme liste. Par exemple, cette foreach traverse des périphériques matériels. Inner foreach récupère plusieurs données de chaque appareil. Selon l'exigence, je dois enregistrer dans la base de données les données après les avoir récupérées un par un sur chaque appareil. Ce n'est pas une option pour enregistrer toutes les données à la fin du processus. J'ai rencontré la même erreur mais la solution de mzonerz a fonctionné.
jstuardo

@jstuardo Même avec la mise en lots?
Dinerdo

@Dinerdo Je suis d'accord que ce n'est pas une bonne pratique au niveau philosophique. Cependant, il existe plusieurs situations où à l'intérieur de la boucle for le code appelle une autre méthode (disons une méthode AddToLog ()) qui inclut un appel à db.SaveChanges () localement. Dans cette situation, vous ne pouvez pas vraiment contrôler l'appel à db.Save Changes. Dans ce cas, l'utilisation d'une ToList () ou d'une structure similaire fonctionnera comme suggéré par mzonerz. Merci!
A. Varma

En pratique, cela vous fera plus de mal que cela ne vous aidera. Je maintiens ce que j'ai dit - ToList () ne devrait certainement pas être utilisé tout le temps, et l'enregistrement des modifications après chaque élément est quelque chose à éviter dans la mesure du possible dans une application haute performance. Ce serait un correctif temporaire IMO. Quelle que soit la méthode de journalisation que vous avez, vous devriez également idéalement profiter de la mise en mémoire tampon.
Dinerdo

8

FYI: à partir d'un livre et de quelques lignes ajustées parce que son stil valide:

L'appel de la méthode SaveChanges () commence une transaction qui annule automatiquement toutes les modifications persistantes dans la base de données si une exception se produit avant la fin de l'itération; sinon la transaction est validée. Vous pourriez être tenté d'appliquer la méthode après chaque mise à jour ou suppression d'entité plutôt qu'après la fin de l'itération, en particulier lorsque vous mettez à jour ou supprimez un grand nombre d'entités.

Si vous essayez d'appeler SaveChanges () avant que toutes les données aient été traitées, vous encourez une exception «Nouvelle transaction n'est pas autorisée car il existe d'autres threads en cours d'exécution dans la session». L'exception se produit car SQL Server ne permet pas de démarrer une nouvelle transaction sur une connexion qui a un SqlDataReader ouvert, même avec plusieurs jeux d'enregistrements actifs (MARS) activés par la chaîne de connexion (la chaîne de connexion par défaut d'EF active MARS)

Parfois, il vaut mieux comprendre pourquoi les choses se passent ;-)


1
Un bon moyen d'éviter cela est lorsque vous avez un lecteur ouvert pour en ouvrir un second et placer ces opérations dans le deuxième lecteur. C'est quelque chose dont vous pouvez avoir besoin lorsque vous mettez à jour le maître / les détails dans le cadre d'entité. Vous ouvrez la première connexion pour l'enregistrement principal et la seconde pour les enregistrements de détail. si vous ne faites que lire, il ne devrait y avoir aucun problème. les problèmes surviennent lors de la mise à jour.
Herman Van Der Blom

Explication utile. vous avez raison, il est bon de comprendre pourquoi les choses se passent.
Dov Miller

8

Faire vos listes interrogables dans .ToList () et cela devrait fonctionner correctement.


1
Veuillez fournir un exemple au lieu de simplement publier une solution.
Ronnie Oosting

5

J'obtenais ce même problème mais dans une situation différente. J'avais une liste d'articles dans une zone de liste. L'utilisateur peut cliquer sur un élément et sélectionner supprimer, mais j'utilise un proc stocké pour supprimer l'élément, car la suppression de l'élément nécessite beaucoup de logique. Lorsque j'appelle le proc stocké, la suppression fonctionne correctement, mais tout appel ultérieur à SaveChanges entraînera l'erreur. Ma solution a été d'appeler le proc stocké en dehors d'EF et cela a bien fonctionné. Pour une raison quelconque, lorsque j'appelle le proc stocké en utilisant la manière EF de faire les choses, cela laisse quelque chose de ouvert.


3
J'ai eu un problème similaire récemment: la raison dans mon cas était une SELECTdéclaration dans une procédure stockée qui produisait un jeu de résultats vide et si ce jeu de résultats n'était pas lu, SaveChangeslevait cette exception.
2014

Même chose avec le résultat non lu de SP, merci beaucoup pour un indice)
Pavel K

4

Voici 2 autres options qui vous permettent d'appeler SaveChanges () dans un pour chaque boucle.

La première option consiste à utiliser un DBContext pour générer vos objets de liste à parcourir, puis à créer un deuxième DBContext pour appeler SaveChanges (). Voici un exemple:

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

La deuxième option consiste à obtenir une liste des objets de base de données à partir du DBContext, mais à ne sélectionner que les identifiants. Et puis parcourez la liste des id (vraisemblablement un int) et obtenez l'objet correspondant à chaque int, et appelez SaveChanges () de cette façon. L'idée derrière cette méthode est de saisir une grande liste d'entiers, est beaucoup plus efficace que d'obtenir une grande liste d'objets db et d'appeler .ToList () sur l'objet entier. Voici un exemple de cette méthode:

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}

C'est une excellente alternative à laquelle j'ai pensé et fait, mais cela doit être surévalué. Remarque: i) vous pouvez itérer comme énumérable, ce qui est bon pour les très grands ensembles; ii) Vous pouvez utiliser la commande NoTracking pour éviter les problèmes de chargement d'autant d'enregistrements (si tel est votre scénario); iii) J'aime vraiment aussi l'option de clé primaire uniquement - c'est très intelligent parce que vous chargez beaucoup moins de données en mémoire, mais vous ne traitez pas avec Take / Skip sur un ensemble de données sous-jacent potentiellement dynamique.
Todd

4

Si vous obtenez cette erreur en raison de foreach et que vous devez vraiment enregistrer une entité en premier dans la boucle et utiliser l'identité générée plus loin dans la boucle, comme c'était le cas dans mon cas, la solution la plus simple consiste à utiliser un autre DBContext pour insérer une entité qui renverra l'ID et utilisera cet identifiant dans un contexte externe

Par exemple

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }

2

Donc, dans le projet, j'avais exactement le même problème, le problème n'était pas dans le foreachou .toList()c'était en fait dans la configuration AutoFac que nous avons utilisée. Cela a créé des situations étranges où l'erreur ci-dessus a été générée, mais également un tas d'autres erreurs équivalentes.

C'était notre solution: cela a changé:

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

À:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();

Pourriez-vous préciser quel était, selon vous, le problème? vous avez résolu cela en créant à chaque fois un nouveau Dbcontext?
eran otzap

2

Je sais que c'est une vieille question mais j'ai fait face à cette erreur aujourd'hui.

et j'ai trouvé que cette erreur peut être levée lorsqu'un déclencheur de table de base de données obtient une erreur.

pour votre information, vous pouvez également vérifier vos déclencheurs de tables lorsque vous obtenez cette erreur.


2

J'avais besoin de lire un énorme ResultSet et de mettre à jour certains enregistrements dans le tableau. J'ai essayé d'utiliser des morceaux comme suggéré dans la réponse de Drew Noakes .

Malheureusement, après 50000 enregistrements, j'ai eu OutofMemoryException. La réponse Entity Framework Large Data Set, Out of Memory Exception explique, que

EF crée une deuxième copie des données qui sert à la détection des modifications (afin qu'il puisse conserver les modifications apportées à la base de données). EF détient ce deuxième ensemble pour la durée de vie du contexte et c'est cet ensemble qui vous fait manquer de mémoire.

La recommandation est de recréer votre contexte pour chaque lot.

J'ai donc récupéré les valeurs minimale et maximale de la clé primaire - les tables ont des clés primaires sous forme d'entiers incrémentiels automatiques, puis j'ai récupéré de la base de données des morceaux d'enregistrements en ouvrant le contexte pour chaque morceau. Après traitement, le contexte de bloc se ferme et libère la mémoire. Il garantit que l'utilisation de la mémoire n'augmente pas.

Voici un extrait de mon code:

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange est une structure simple avec des propriétés From et To.


Je n'ai pas vu comment vous "renouveliez" votre contexte. Il semble que vous créez simplement un nouveau contexte pour chaque bloc.
Suncat2000

@ Suncat2000, vous avez raison, le contexte doit être un objet de courte durée stackoverflow.com/questions/43474112/…
Michael Freidgeim

2

Nous avons commencé à voir cette erreur «La nouvelle transaction n'est pas autorisée car il y a d'autres threads en cours d'exécution dans la session» après la migration d'EF5 vers EF6.

Google nous a amenés ici, mais nous n'appelons pas SaveChanges()dans la boucle. Les erreurs ont été générées lors de l'exécution d'une procédure stockée à l'aide de ObjectContext.ExecuteFunction dans une lecture de boucle foreach à partir de la base de données.

Tout appel à ObjectContext.ExecuteFunction encapsule la fonction dans une transaction. Le début d'une transaction alors qu'il existe déjà un lecteur ouvert provoque l'erreur.

Il est possible de désactiver l'habillage du SP dans une transaction en définissant l'option suivante.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

L' EnsureTransactionsForFunctionsAndCommandsoption permet au SP de s'exécuter sans créer sa propre transaction et l'erreur n'est plus déclenchée.

DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands, propriété


1

J'étais également confronté au même problème.

Voici la cause et la solution.

http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx

Assurez-vous qu'avant de lancer des commandes de manipulation de données telles que des insertions, des mises à jour, vous avez fermé tous les lecteurs SQL actifs précédents.

L'erreur la plus courante concerne les fonctions qui lisent les données de la base de données et renvoient des valeurs. Par exemple pour des fonctions comme isRecordExist.

Dans ce cas, nous revenons immédiatement de la fonction si nous avons trouvé l'enregistrement et oublions de fermer le lecteur.


7
Que signifie «fermer un lecteur» dans Entity Framework? Il n'y a pas de lecteur visible dans une requête comme var result = from customer dans myDb.Customers où customer.Id == customerId select customer; renvoie result.FirstOrDefault ();
Anthony

@Anthony Comme d'autres réponses le disent, si vous utilisez EF pour énumérer une requête LINQ (IQueryable), le DataReader sous-jacent restera ouvert jusqu'à ce que la dernière ligne soit itérée. Mais bien que MARS soit une fonctionnalité importante à activer dans une chaîne de connexion, le problème dans l'OP n'est toujours pas résolu avec MARS seul. Le problème tente de SaveChanges alors qu'un DataReader sous-jacent est toujours ouvert.
Todd

1

Le code ci-dessous fonctionne pour moi:

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}

2
Bienvenue chez SO! Pensez à ajouter une explication et / ou des liens décrivant pourquoi cela fonctionne pour vous. Les réponses de code uniquement sont généralement considérées comme de mauvaise qualité pour SO.
codeMagic

1

Dans mon cas, le problème est apparu lorsque j'ai appelé la procédure stockée via EF, puis que SaveChanges a levé cette exception. Le problème était d'appeler la procédure, l'énumérateur n'était pas disposé. J'ai corrigé le code de la manière suivante:

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}

0

Je suis beaucoup en retard à la fête, mais aujourd'hui, j'ai fait face à la même erreur et la façon dont j'ai résolu était simple. Mon scénario était similaire à ce code donné, je faisais des transactions DB à l'intérieur de boucles imbriquées pour chaque.

Le problème est qu'une transaction à base de données unique prend un peu plus de temps que pour chaque boucle, donc une fois que la transaction précédente n'est pas terminée, la nouvelle traction lève une exception, la solution consiste donc à créer un nouvel objet dans la boucle pour chaque boucle. où vous effectuez une transaction db.

Pour les scénarios mentionnés ci-dessus, la solution sera la suivante:

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                    {
                        var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                        foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                        {
                            foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                            {
                                if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                            {
                                var newProduct = new RivWorks.Model.Negotiation.Product();
                                newProduct.alternateProductID = sourceProduct.AutoID;
                                newProduct.isFromFeed = true;
                                newProduct.isDeleted = false;
                                newProduct.SKU = sourceProduct.StockNumber;
                                company.Product.Add(newProduct);
                            }
                        }
                        _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                    }
                }

0

Je suis un peu en retard, mais j'ai aussi eu cette erreur. J'ai résolu le problème en vérifiant où les valeurs étaient mises à jour.

J'ai découvert que ma requête était erronée et qu'il y avait plus de 250+ modifications en attente. J'ai donc corrigé ma requête, et maintenant cela fonctionne correctement.

Donc, dans ma situation: recherchez les erreurs dans la requête, en déboguant le résultat renvoyé par la requête. Après cela, corrigez la requête.

J'espère que cela aidera à résoudre les problèmes futurs.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.