SqlException da Entity Framework: la nuova transazione non è consentita perché nella sessione sono in esecuzione altri thread


600

Attualmente sto ricevendo questo errore:

System.Data.SqlClient.SqlException: la nuova transazione non è consentita perché nella sessione sono in esecuzione altri thread.

durante l'esecuzione di questo codice:

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

Modello n. 1: questo modello si trova in un database sul nostro Dev Server. Modello n. 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Modello n. 2: questo modello si trova in un database sul nostro server di produzione e viene aggiornato ogni giorno da feed automatici. testo alternativo http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Nota: gli elementi cerchiati in rosso nel Modello n. 1 sono i campi che utilizzo per "mappare" sul Modello n. 2. Si prega di ignorare i cerchi rossi nel Modello n. 2: quello è da un'altra domanda che ho avuto a cui ora viene data una risposta.

Nota: devo ancora inserire un controllo isDeleted in modo da poterlo eliminare delicatamente da DB1 se è uscito dall'inventario dei nostri clienti.

Tutto ciò che voglio fare, con questo particolare codice, è connettere un'azienda in DB1 con un client in DB2, ottenere il loro elenco di prodotti da DB2 e INSERIRLO in DB1 se non è già presente. La prima volta dovrebbe essere una vera attrazione dell'inventario. Ogni volta che viene eseguito lì dopo che non dovrebbe accadere nulla a meno che non sia arrivato nuovo inventario nel feed durante la notte.

Quindi la grande domanda: come posso risolvere l'errore di transazione che sto ricevendo? Devo abbandonare e ricreare il mio contesto ogni volta attraverso i loop (non ha senso per me)?


6
Questa è la domanda più dettagliata che abbia mai visto.

9
Qualcuno manca ancora le procedure memorizzate?
David,

Risposte:


690

Dopo aver tolto molto i capelli ho scoperto che i foreachcappi erano i colpevoli. Ciò che deve accadere è chiamare EF ma riportarlo in uno IList<T>di quel tipo di destinazione, quindi fare un ciclo sul IList<T>.

Esempio:

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
Sì, anche questo mi ha causato mal di testa. Sono quasi caduto dalla sedia quando ho riscontrato il problema! Comprendo le ragioni tecniche alla base del problema, ma questo non è intuitivo e non aiuta lo sviluppatore a cadere nella "fossa del successo" blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Doctor Jones,

9
Non è male per le prestazioni per set di dati di grandi dimensioni? Se hai milioni di record nella tabella. ToList () li risucchia tutti nella memoria. Sto riscontrando questo problema e mi chiedevo se sarebbe possibile quanto segue a) Staccare l'entità b) Creare un nuovo ObjectContext e collegare l'entità staccata ad esso. c) Chiama SaveChanges () sul nuovo ObjectContext d) Scollega l'entità dal nuovo ObjectContext e) Ricollegalo al vecchio ObjectContext
Abhijeet Patel

150
Il problema è che non puoi chiamare SaveChangesmentre stai ancora estraendo risultati dal DB. Pertanto un'altra soluzione è solo quella di salvare le modifiche una volta completato il ciclo.
Drew Noakes,

4
Dopo essere stato morso, ho aggiunto questo a Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/… Sentiti libero di votarlo.
Ian Mercer,

36
I nostri sviluppatori tendono ad aggiungere .ToList () a qualsiasi query LINQ senza pensare alle conseguenze. Questa è la prima volta che aggiungi .ToList () è davvero utile!
Marc,

267

Come hai già identificato, non puoi salvare da un oggetto foreachche sta ancora attingendo dal database tramite un lettore attivo.

Chiamare ToList()o ToArray()va bene per piccoli set di dati, ma quando hai migliaia di righe, consumerai una grande quantità di memoria.

È meglio caricare le righe in blocchi.

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

Dati i metodi di estensione sopra, puoi scrivere la tua query in questo modo:

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

È necessario ordinare l'oggetto interrogabile su cui si chiama questo metodo. Questo perché Entity Framework supporta solo le IQueryable<T>.Skip(int)query ordinate, il che ha senso se si considera che più query per intervalli diversi richiedono che l'ordinamento sia stabile. Se l'ordinamento non è importante per te, basta ordinare per chiave primaria in quanto è probabile che abbia un indice cluster.

Questa versione interrogherà il database in batch di 100. Si noti che SaveChanges()viene chiamato per ogni entità.

Se vuoi migliorare drasticamente il tuo rendimento, dovresti chiamare SaveChanges()meno frequentemente. Usa invece il codice in questo modo:

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

Ciò comporta 100 volte meno chiamate di aggiornamento del database. Ovviamente ognuna di quelle chiamate richiede più tempo per essere completata, ma alla fine si esce comunque molto avanti. Il tuo chilometraggio può variare, ma per me i mondi sono stati più veloci.

E aggira l'eccezione che stavi vedendo.

EDIT Ho rivisitato questa domanda dopo aver eseguito SQL Profiler e ho aggiornato alcune cose per migliorare le prestazioni. Per chiunque sia interessato, ecco alcuni esempi di SQL che mostrano ciò che viene creato dal DB.

Il primo ciclo non ha bisogno di saltare nulla, quindi è più semplice.

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

Le chiamate successive devono saltare blocchi di risultati precedenti, quindi introduce l'uso di 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
Grazie. La tua spiegazione è stata molto più utile di quella contrassegnata come "Risposta".
Wagner da Silva,

1
Questo è fantastico solo una cosa: se si esegue una query su una colonna e si aggiorna il valore di quella colonna, è necessario essere consapevoli di chunkNumber ++; . Supponiamo che tu abbia una colonna "ModifiedDate" e stai interrogando .Where (x => x.ModifiedDate! = Null) e alla fine di foreach hai impostato un valore per ModifiedDate. In questo modo non stai ripetendo la metà dei record perché la metà dei record viene ignorata.
Arvand,


Penso che dovrebbe funzionare. var skip = 0; const int take = 100; Elenco emps <Employee>; while ((emps = db.Employees.Skip (skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Fai cose qui}} Vorrei formulare una risposta ma sarebbe sepolta sotto le pile di risposte qui sotto e si riferisce a questa domanda.
jwize,

123

Ora abbiamo pubblicato una risposta ufficiale al bug aperto su Connect . Le soluzioni alternative che consigliamo sono le seguenti:

Questo errore è dovuto al fatto che Entity Framework crea una transazione implicita durante la chiamata SaveChanges (). Il modo migliore per aggirare l'errore consiste nell'utilizzare un modello diverso (ovvero, non salvare durante la lettura) o dichiarare esplicitamente una transazione. Ecco tre possibili soluzioni:

// 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
Se prendi il percorso Transaction, semplicemente inserendo un TransactionScope potrebbe non risolverlo - non dimenticare di estendere il Timeout se ciò che stai facendo potrebbe richiedere molto tempo - ad esempio se eseguirai il debug interattivo del codice facendo il Chiamata DB. Ecco il codice che estende il timeout della transazione a un'ora: using (var transazione = new TransactionScope (TransactionScopeOption.Rired, new TimeSpan (1, 0, 0)))
Chris Moschini

Mi sono imbattuto in questo errore la prima volta che ho divagato dal "percorso tutorial" in un vero esempio da solo! Per me, tuttavia, la soluzione più semplice, SALVA DOPO ITERAZIONE, meglio è! (Penso che il 99% delle volte sia il caso, e solo l'1% DEVE davvero eseguire un database salvando INSIDE the loop)
spiderman

Schifoso. Ho appena incontrato questo errore. Molto cattivo. Il secondo suggerimento ha funzionato come un incantesimo per me insieme a spostare i miei SaveChanges nel loop. Pensavo che salvare le modifiche al di fuori del ciclo fosse migliore per le modifiche in batch. Ma va bene Non credo?! :(
Mr. Young,

Non ha funzionato per me. NET 4.5. Quando ho usato TransactionScope ho ricevuto il seguente errore "Il provider sottostante non è riuscito su EnlistTransaction. {" Il gestore delle transazioni del partner ha disabilitato il supporto per le transazioni remote / di rete. (Eccezione da HRESULT: 0x8004D025) "}". Finisco per fare il lavoro al di fuori dell'iterazione.
Diganta Kumar,

L'uso di TransactionScope è pericoloso, poiché la tabella è bloccata per il tempo dell'intera transazione.
Michael Freidgeim,

19

In effetti non è possibile salvare le modifiche all'interno di un foreachciclo in C # utilizzando Entity Framework.

context.SaveChanges() Il metodo si comporta come un commit su un normale sistema di database (RDMS).

Apporta tutte le modifiche (che Entity Framework memorizzerà nella cache) e poi salvale tutte in una volta chiamando SaveChanges()dopo il ciclo (al di fuori di esso), come un comando di commit del database.

Funziona se puoi salvare tutte le modifiche contemporaneamente.


2
Ho pensato che fosse interessante vedere qui "RDMS (Regular Database System)"
Dinerdo il

1
Questo sembra sbagliato, dal momento che chiamare ripetutamente SaveChanges va bene nel 90% dei contesti in EF.
Pxtl,

Sembra che chiamare ripetutamente SaveChanges vada bene, a meno che il ciclo foreach non stia iterando su un'entità db.
Kerbasaurus,

1
Aha! Porta il contesto all'interno di ogni ciclo! (pffft ... cosa stavo pensando? ..) Grazie!
Adam Cox,

18

context.SaveChanges()Metti solo dopo la fine del tuo foreach(loop).


Questa è l'opzione migliore che ho scoperto nel mio caso a causa del salvataggio all'interno di foreach
Almeida

2
Questa non è sempre un'opzione.
Pxtl,

9

Usa sempre la tua selezione come Elenco

Per esempio:

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

Quindi scorrere la raccolta mentre si salvano le modifiche

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

             }

1
Questa non è affatto una buona pratica. Non dovresti eseguire SaveChanges così spesso se non ne hai bisogno, e sicuramente non dovresti "Usare sempre la tua selezione come Elenco"
Dinerdo

@Dinerdo dipende davvero dallo scenario. Nel mio caso, ho 2 loop foreach. Uno esterno aveva la query db come elenco. Ad esempio, questo foreach attraversa i dispositivi hardware. Foreach interno recupera diversi dati da ciascun dispositivo. Come da requisito, devo salvare nel database i dati dopo che sono stati recuperati da ogni dispositivo uno per uno. Non è un'opzione per salvare tutti i dati alla fine del processo. Ho riscontrato lo stesso errore ma la soluzione di mzonerz ha funzionato.
jstuardo,

@jstuardo Anche con il batch?
Dinerdo,

@Dinerdo Sono d'accordo che non è una buona pratica a livello filosofico. Tuttavia, esistono diverse situazioni in cui all'interno del ciclo for il codice chiama un altro metodo (diciamo un metodo AddToLog ()) che include una chiamata a db.SaveChanges () localmente. In questa situazione non puoi davvero controllare la chiamata a db. Salva modifiche. In questo caso, l'utilizzo di una ToList () o di una struttura simile funzionerà come suggerito da mzonerz. Grazie!
A. Varma

In pratica, questo ti farà più male di quanto possa aiutarti. Sono fedele a quello che ho detto: ToList () sicuramente non dovrebbe essere usato sempre, e salvare le modifiche dopo ogni singolo elemento è qualcosa da evitare ovunque sia possibile in un'app ad alte prestazioni. Questa sarebbe una soluzione temporanea IMO. Qualunque metodo di registrazione tu abbia, dovresti anche idealmente approfittare del buffering.
Dinerdo,

8

Cordiali saluti: da un libro e alcune righe adattate perché il suo stil valido:

Il richiamo del metodo SaveChanges () avvia una transazione che ripristina automaticamente tutte le modifiche persistenti al database se si verifica un'eccezione prima del completamento dell'iterazione; altrimenti la transazione si impegna. Potresti essere tentato di applicare il metodo dopo ogni aggiornamento o eliminazione di un'entità anziché dopo il completamento dell'iterazione, in particolare quando aggiorni o elimini un numero enorme di entità.

Se si tenta di invocare SaveChanges () prima che tutti i dati siano stati elaborati, si incorre in un'eccezione "Nuova transazione non consentita perché ci sono altri thread in esecuzione nella sessione". L'eccezione si verifica perché SQL Server non consente l'avvio di una nuova transazione su una connessione con SqlDataReader aperto, anche con MARS (Multiple Active Record Set) abilitato dalla stringa di connessione (la stringa di connessione predefinita di EF abilita MARS)

A volte è meglio capire perché le cose stanno accadendo ;-)


1
Un buon modo per evitarlo è quando hai un lettore aperto per aprirne uno secondo e mettere quelle operazioni nel secondo lettore. Questo è qualcosa di cui puoi aver bisogno quando aggiorni i dettagli / master nel framework dell'entità. Si apre la prima connessione per il record principale e la seconda per i record di dettaglio. se stai solo leggendo non dovrebbero esserci problemi. i problemi si verificano durante l'aggiornamento.
Herman Van Der Blom,

Spiegazione utile. hai ragione, è bene capire perché le cose stanno accadendo.
Dov Miller,


5

Stavo riscontrando lo stesso problema ma in una situazione diversa. Avevo un elenco di elementi in una casella di riepilogo. L'utente può fare clic su un elemento e selezionare Elimina, ma sto usando un proc memorizzato per eliminare l'elemento perché c'è molta logica coinvolta nell'eliminazione dell'elemento. Quando chiamo il proc memorizzato, l'eliminazione funziona correttamente ma qualsiasi chiamata futura a SaveChanges causerà l'errore. La mia soluzione era chiamare il proc memorizzato al di fuori di EF e questo ha funzionato bene. Per qualche ragione quando chiamo il proc memorizzato usando il modo EF di fare le cose lascia qualcosa di aperto.


3
Recentemente ho avuto un problema simile: il motivo nel mio caso era SELECTun'istruzione nella procedura memorizzata che produceva un set di risultati vuoto e se tale set di risultati non veniva letto, SaveChangesgenerava tale eccezione.
n0rd

Stessa cosa con risultato non letto di SP, grazie mille per il suggerimento)
Pavel K

4

Ecco altre 2 opzioni che ti consentono di invocare SaveChanges () in a per ogni ciclo.

La prima opzione è utilizzare un DBContext per generare gli oggetti elenco per scorrere, quindi creare un secondo DBContext per chiamare SaveChanges (). Ecco un esempio:

//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 seconda opzione è quella di ottenere un elenco di oggetti di database da DBContext, ma di selezionare solo gli ID. E quindi scorrere l'elenco degli ID (presumibilmente un int) e ottenere l'oggetto corrispondente a ciascun int e invocare SaveChanges () in quel modo. L'idea alla base di questo metodo è quella di afferrare un ampio elenco di numeri interi, è molto più efficiente quindi ottenere un ampio elenco di oggetti db e chiamare .ToList () sull'intero oggetto. Ecco un esempio di questo metodo:

//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()
}

Questa è un'ottima alternativa che ho pensato e fatto, ma deve essere votata. Nota: i) puoi iterare come enumerabile che è buono per insiemi molto grandi; ii) Puoi usare il comando NoTracking per evitare problemi con il caricamento di così tanti record (se questo è il tuo scenario); iii) Mi piace molto anche l'unica opzione della chiave primaria: è molto intelligente perché stai caricando molti meno dati in memoria, ma non hai a che fare con Take / Skip su un set di dati potenzialmente dinamico.
Todd,

4

Se ricevi questo errore a causa di foreach e devi davvero salvare prima un'entità all'interno del ciclo e utilizzare l'identità generata più avanti nel ciclo, come nel mio caso, la soluzione più semplice è utilizzare un altro DBContext per inserire un'entità che restituirà Id e userà questo ID nel contesto esterno

Per esempio

    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

Quindi nel progetto ho avuto lo stesso identico problema che il problema non riguardava foreacho .toList()era effettivamente nella configurazione AutoFac che abbiamo usato. Ciò ha creato alcune situazioni strane in cui è stato generato l'errore sopra riportato ma sono stati anche lanciati un sacco di altri errori equivalenti.

Questa era la nostra soluzione: cambiato questo:

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

Per:

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

Potresti elaborare quale pensi che sia stato il problema? hai risolto questo creando ogni volta un nuovo Dbcontext?
Eran Otzap,

2

So che è una vecchia domanda, ma oggi ho riscontrato questo errore.

e ho scoperto che, questo errore può essere generato quando un trigger di tabella del database ottiene un errore.

per tua informazione, puoi controllare anche i trigger delle tue tabelle quando ricevi questo errore.


2

Avevo bisogno di leggere un enorme ResultSet e aggiornare alcuni record nella tabella. Ho cercato di usare pezzi come suggerito nel Drew Noakes 's risposta .

Purtroppo dopo 50000 dischi ho OutofMemoryException. La risposta spiega: Entity framework data set di grandi dimensioni, eccezione di memoria esaurita

EF crea una seconda copia di dati che utilizza per il rilevamento delle modifiche (in modo che possa persistere modifiche al database). EF contiene questo secondo set per la durata del contesto ed è questo set che ti sta esaurendo la memoria.

La raccomandazione è di ricreare il contesto per ciascun batch.

Quindi ho recuperato i valori Minimo e Massimo della chiave primaria: le tabelle hanno le chiavi primarie come interi incrementali automatici, quindi ho recuperato dai blocchi di database dei record aprendo il contesto per ogni blocco. Dopo l'elaborazione, il contesto del blocco si chiude e rilascia la memoria. Assicura che l'utilizzo della memoria non sta crescendo.

Di seguito è riportato uno snippet dal mio codice:

  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 è una struttura semplice con proprietà From e To.


Non sono riuscito a vedere come stavi "rinnovando" il tuo contesto. Sembra che tu stia semplicemente creando un nuovo contesto per ogni blocco.
Suncat2000,

@ Suncat2000, hai ragione, il contesto dovrebbe essere un breve vivente oggetto stackoverflow.com/questions/43474112/...
Michael Freidgeim

2

Abbiamo iniziato a vedere questo errore "Nuova transazione non consentita perché nella sessione sono in esecuzione altri thread" dopo la migrazione da EF5 a EF6.

Google ci ha portato qui ma non stiamo chiamando SaveChanges()all'interno del loop. Gli errori sono stati generati durante l'esecuzione di una procedura memorizzata utilizzando ObjectContext.ExecuteFunction all'interno di un ciclo foreach che legge dal DB.

Qualsiasi chiamata a ObjectContext.ExecuteFunction avvolge la funzione in una transazione. L'inizio di una transazione mentre è già presente un lettore aperto causa l'errore.

È possibile disabilitare il wrapping dell'SP in una transazione impostando la seguente opzione.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

L' EnsureTransactionsForFunctionsAndCommandsopzione consente l'esecuzione dell'SP senza creare la propria transazione e l'errore non viene più generato.

Proprietà DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands


1

Stavo anche affrontando lo stesso problema.

Ecco la causa e la soluzione.

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

Assicurati prima di lanciare comandi di manipolazione dei dati come inserti, aggiornamenti, hai chiuso tutti i precedenti lettori SQL attivi.

L'errore più comune sono le funzioni che leggono i dati da db e restituiscono valori. Ad esempio funzioni come isRecordExist.

In questo caso torniamo immediatamente dalla funzione se abbiamo trovato il record e dimentichiamo di chiudere il lettore.


7
Che cosa significa "chiudere un lettore" in Entity Framework? Non esiste un lettore visibile in una query come var result = from customer in myDb.Customers dove customer.Id == customerId seleziona cliente; return result.FirstOrDefault ();
Anthony,

@Anthony Come dicono altre risposte, se si utilizza EF per enumerare una query LINQ (IQueryable), il DataReader sottostante rimarrà aperto fino a quando non viene ripetuta l'ultima riga. Ma sebbene MARS sia una caratteristica importante da abilitare in una stringa di connessione, il problema nell'OP non è ancora risolto con MARS da solo. Il problema sta provando a SaveChanges mentre un DataReader sottostante è ancora aperto.
Todd,

1

Il codice qui sotto funziona per me:

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
Benvenuti in SO! Prendi in considerazione l'aggiunta di una spiegazione e / o collegamenti che descrivano il motivo per cui questo funziona per te. Le risposte di solo codice sono generalmente considerate non di buona qualità per SO.
codeMagic

1

Nel mio caso, il problema è apparso quando ho chiamato Stored Procedure tramite EF e successivamente SaveChanges ha lanciato questa eccezione. Il problema era nel chiamare la procedura, l'enumeratore non era disposto. Ho corretto il codice nel modo seguente:

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

Sono in ritardo alla festa, ma oggi ho riscontrato lo stesso errore e il modo in cui ho risolto era semplice. Il mio scenario era simile a questo codice dato che stavo facendo transazioni DB all'interno di cicli annidati per ogni.

Il problema è che una transazione DB singola impiega un po 'più di tempo rispetto al ciclo for-each, quindi una volta che la transazione precedente non è completa, la nuova trazione genera un'eccezione, quindi la soluzione è creare un nuovo oggetto nel ciclo for-each dove stai effettuando una transazione db.

Per gli scenari sopra menzionati la soluzione sarà così:

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

Sono un po 'in ritardo, ma ho avuto anche questo errore. Ho risolto il problema controllando quali sono i valori che sono stati aggiornati.

Ho scoperto che la mia query era errata e che lì erano in sospeso oltre 250 modifiche. Quindi ho corretto la mia query e ora funziona correttamente.

Quindi, nella mia situazione: controllare la query per errori, eseguendo il debug sul risultato restituito dalla query. Dopodiché correggi la query.

Spero che questo aiuti a risolvere i problemi futuri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.