SqlException à partir du Framework Entity-nouvelle transaction n'est pas autorisée car il y a d'autres threads en cours d'exécution dans la session

je reçois actuellement cette erreur:

Système

.Données.SqlClient.SqlException: la nouvelle transaction n'est pas autorisée parce qu'il y a d'autres threads en cours d'exécution dans la session.

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

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

Modèle #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. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Note - les éléments encerclés en rouge dans le modèle #1 sont les les champs que j'utilise pour "cartographier" le modèle #2. S'il vous plaît, ignorez les cercles rouges dans le modèle 2: c'est d'une autre question que j'ai eu qui est maintenant répondu.

Note: j'ai encore besoin de mettre un contrôle isDeleted afin que je puisse le supprimer 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 de DB2 et L'insérer dans DB1 si elle n'est pas déjà là. Première fois devrait être une pleine tirer de l'inventaire. Chaque fois qu'il est exécuté là-bas après que rien ne devrait se produire à moins que le nouvel inventaire est venu sur la nourriture au cours de la nuit.

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

510
demandé sur Todd 2010-01-22 01:37:14

19 réponses

après avoir retiré beaucoup de cheveux, j'ai découvert que les boucles foreach étaient les coupables. Ce qu'il faut faire, c'est appeler EF mais le retourner dans un IList<T> de ce type de cible puis boucle 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;
    // ...
}
586
répondu Keith Barrows 2010-02-01 23:54:26

comme vous l'avez déjà identifié, vous ne pouvez pas sauvegarder dans un foreach qui est encore en train de tirer à partir de la base de données via un lecteur actif.

appeler ToList() ou ToArray() est bien pour les petits ensembles de données, mais quand vous avez des milliers de lignes, vous consommerez une grande quantité de mémoire.

il est préférable de charger les rangées 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++;
        }
    }
}

étant donné les méthodes d'extension ci-dessus, vous pouvez écrire votre requête comme ceci:

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

l'objet queryable sur lequel vous appelez cette méthode doit être commandé. c'est parce que Entity Framework supporte seulement IQueryable<T>.Skip(int) sur les requêtes ordonnées, ce qui est logique quand vous considérez que les requêtes multiples pour différentes plages nécessitent l'ordre pour être stable. Si l'ordre n'est pas important pour vous, il suffit d'ordre par clé primaire car cela est susceptible d'avoir un indice groupé.

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

si vous voulez améliorer votre débit de façon spectaculaire, vous devriez appeler SaveChanges() moins fréquemment. Utilisez plutôt le code suivant:

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

il en résulte 100 fois moins d'appels de mise à jour de base de données. Bien sûr, chacun de ces appels prend plus de temps à remplir, mais vous êtes toujours en avance à la fin. Votre kilométrage peut varier, mais il a été mondes plus rapide pour moi.

et il obtient autour de l'exception que vous voyiez.

EDIT j'ai revu cette question après avoir lancé SQL Profiler et mis à jour quelques éléments pour améliorer les performances. Pour quiconque est intéressé, voici un exemple de SQL qui montre ce qui est créé par la base de données.

la première boucle n'a pas besoin de sauter quoi que ce soit, il 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 sauter les 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
243
répondu Drew Noakes 2010-12-21 21:19:06

nous avons maintenant posté une réponse officielle à le bug s'est ouvert sur la connexion . Les solutions de rechange que nous recommandons sont les suivantes:

cette erreur est due au fait que Entity Framework crée une transaction implicite lors de L'appel SaveChanges (). La meilleure façon de contourner l'erreur est d'utiliser un modèle différent (c'est-à-dire de ne pas sauvegarder pendant la lecture) ou en déclarant 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();
    }
} 
114
répondu Mark Stafford - MSFT 2014-05-15 11:00:34

vient de mettre context.SaveChanges() après la fin de votre foreach (boucle).

13
répondu Majid 2017-07-06 12:05:20

en effet, vous ne pouvez pas enregistrer des modifications à l'intérieur d'une boucle foreach dans C# en utilisant Entity Framework.

context.SaveChanges() la méthode agit comme un commit sur un système de base de données régulier (RDMS).

il suffit de faire toutes les modifications (quel cadre D'entité sera mis en cache) et ensuite de les enregistrer toutes en appelant SaveChanges() après la boucle (en dehors de celle-ci), comme une commande de propagation de la base de données.

cela fonctionne si vous pouvez enregistrer toutes les modifications à la fois.

5
répondu Edgardo Pichardo C. 2018-06-25 19:41:21

j'ai eu ce même problème mais dans une situation différente. J'avais une liste d'éléments dans une liste déroulante. L'utilisateur peut cliquer sur un élément et sélectionner supprimer, mais j'utilise un proc stocké pour supprimer l'élément parce qu'il ya beaucoup de logique impliqué dans la suppression de l'élément. Quand j'appelle le proc stocké la suppression fonctionne très bien mais tout appel futur à SaveChanges causera l'erreur. Ma solution était d'appeler le proc stocké à l'extérieur de EF et cela a fonctionné très bien. Pour une raison quelconque, quand j'appelle la procédure stockée en utilisant la méthode EF, on laisse quelque chose ouvert.

4
répondu MikeKulls 2011-05-25 00:21:29

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

la méthode D'invocation de SaveChanges() commence une transaction qui renverse automatiquement toutes les modifications qui ont persisté dans la base de données si une exception se produit avant que l'itération ne soit terminée; sinon, la transaction s'engage. Vous pourriez être tenté d'appliquer la méthode après chaque mise à jour ou suppression d'entité plutôt qu'après itération complète, en particulier lorsque vous mettez à jour ou supprimer des nombres massifs de entité.

si vous essayez d'invoquer SaveChanges() avant que toutes les données aient été traitées, vous encourez une" nouvelle transaction n'est pas autorisée parce qu'il y a d'autres threads en cours d'exécution dans la session " exception. L'exception se produit parce que SQL Server ne permet pas de lancer une nouvelle transaction sur une connexion qui a un SqlDataReader ouvert, même avec plusieurs ensembles D'enregistrements actifs (MARS) activé par la chaîne de connexion (la chaîne de connexion par défaut de EF permet MARS)

Il est parfois préférable de comprendre pourquoi les choses se produisent; -)

3
répondu Herman Van Der Blom 2014-01-13 15:28:59

voici 2 autres options qui vous permettent d'invoquer SaveChanges() en a pour chaque boucle.

la première option est d'utiliser un DBContext pour générer vos objets de liste à itérer, puis de créer un 2nd DBContext pour appeler SaveChanges() on. 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 est d'obtenir une liste d'objets de base de données à partir du DBContext, mais de sélectionner seulement les id. Et puis itérer à travers la liste des id (probablement un int) et obtenir l'objet correspondant à chaque int, et invoquer SaveChanges () de cette façon. L'idée derrière cette méthode est de prendre une grande liste d'entiers, est beaucoup plus efficace, puis l'obtention d'un grand liste de db objets et d'appel .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()
}
3
répondu jjspierx 2016-08-26 13:12:03

Utilisez toujours votre sélection comme liste

par exemple:

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

puis boucle à travers 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();

             }
3
répondu mzonerz 2017-09-19 06:07:38

donc dans le projet si j'avais ce même problème le problème n'était pas dans le foreach ou le .toList() il était en fait dans la configuration D'AutoFac que nous avons utilisé. Cela a créé quelques situations bizarres étaient l'erreur ci-dessus a été jeté, mais aussi un tas d'autres erreurs équivalentes ont été lancées.

C'était notre solution: Modifié ceci:

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();
2
répondu VeldMuijz 2015-07-21 13:48:26

j'étais également confronté à la même question.

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 avant de lancer des commandes de manipulation de données comme inserts, mises à jour, que vous avez fermé tous les lecteurs SQL actifs précédents.

l'erreur la plus courante est les fonctions qui lisent des données de db et les valeurs de retour. For E. g fonctionne comme isRecordExist.

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

1
répondu Vinod T. Patil 2011-03-10 11:35:10

dans mon cas, le problème est apparu quand j'ai appelé procédure stockée via EF et ensuite SaveChanges jeter cette exception. Le problème était d'appeler la procédure, le recenseur n'a pas été éliminé. J'ai corrigé le code de la façon 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
   }
}
1
répondu qub1n 2015-09-26 15:37:11

j'ai eu besoin de lire un jeu de résultats énorme et mettre à jour quelques enregistrements dans le tableau. J'ai essayé d'utiliser des morceaux comme suggéré dans Drew Noakes 's réponse .

malheureusement, après 50000 enregistrements, J'ai eu une erreur d'impression. La réponse Entity framework grand ensemble de données, de la mémoire exception explique, que

EF crée la deuxième copie de données qui utilise pour le changement de détection (donc qu'il peut persister à modifier la base de données). EF titulaire de cette deuxième série pour la durée de vie du contexte et de ses cet ensemble c'est course vous de la mémoire.

la recommandation est de renouveler votre contexte chaque lot.

donc j'ai récupéré les valeurs minimales et maximales de la clé primaire - les tables ont des clés primaires comme des entiers incrémentiels automatiques.Puis j'ai récupéré dans la base de données des morceaux d'enregistrements en ouvrant le contexte pour chaque morceau. Après le traitement du morceau contexte ferme et libère la mémoire. Il assure que l'utilisation de la mémoire ne s'accroît pas.

ci-dessous est 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 de et à.

1
répondu Michael Freidgeim 2017-05-23 12:02:46

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();
}
0
répondu user2918896 2014-03-25 19:59:27

je suis très en retard au parti 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é que je faisais des transactions DB à l'intérieur de chaque boucle imbriquée.

le problème est comme une seule transaction DB prend un peu plus de temps que pour-chaque boucle donc une fois que la transaction précédente n'est pas terminée alors la nouvelle traction jette une exception, de sorte que la solution est de créer un nouvel objet dans la pour-chaque boucle où vous êtes faire 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
répondu usman 2016-06-03 12:13:05

je suis un peu en retard, mais j'ai eu cette erreur aussi. J'ai résolu le problème en vérifiant ce qui où les valeurs que la mise à jour.

j'ai découvert que ma requête était erronée et qu'il y avait plus de 250 modifications en attente. Donc j'ai corrigé ma requête, et maintenant ça fonctionne correctement.

ainsi dans ma situation: Vérifiez la requête pour les erreurs, en déboguant sur le résultat que la requête renvoie. Après que corriger l' requête.

Espérons que cette aide à la résolution de problèmes à l'avenir.

0
répondu Max 2016-11-08 12:50:38

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 lancée quand un déclencheur de table de base de données obtient une erreur.

pour votre information, vous pouvez vérifier vos déclencheurs de tables aussi quand vous recevez cette erreur.

0
répondu nadir 2017-11-24 13:59:12

si vous obtenez cette erreur due à foreach et que vous avez vraiment besoin d'enregistrer une entité d'abord dans la boucle et d'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 est d'utiliser un autre DBContext pour insérer l'entité qui retournera L'Id et utilisera cet Id dans le contexte extérieur

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();
   }
0
répondu Hemant Sakta 2018-01-15 17:52:51

faisant vos listes de queryable à .ToList() et il devrait fonctionner correctement.

-1
répondu Wojciech Seweryn 2018-08-30 08:15:50