c# - SqlException da Entity Framework: la nuova transazione non è consentita poiché sono presenti altri thread in esecuzione nella sessione





entity-framework transactions inversion-of-control (17)


Stavo ricevendo lo stesso problema ma in una situazione diversa. Ho avuto un elenco di elementi in una casella di riepilogo. L'utente può fare clic su un elemento e selezionare Elimina, ma sto utilizzando un processo memorizzato per eliminare l'elemento perché c'è molta logica nell'eliminazione dell'elemento. Quando chiamo il proc memorizzato, l'eliminazione funziona bene, ma qualsiasi chiamata futura a SaveChanges causerà l'errore. La mia soluzione era di 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 qualcosa, lascia qualcosa di aperto.

Attualmente sto ottenendo questo errore:

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

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 server di sviluppo. 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 Prod e viene aggiornato ogni giorno da feed automatici. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Nota - Gli elementi cerchiati rossi nel Modello # 1 sono i campi che uso per "mappare" al Modello # 2. Si prega di ignorare i cerchi rossi nel Modello n. 2, ovvero da un'altra domanda che ho ricevuto e che ora ha una risposta.

Nota: ho ancora bisogno di inserire un controllo isDeleted in modo da poterlo eliminare facilmente da DB1 se è uscito dall'inventario del nostro cliente.

Tutto quello che voglio fare, con questo particolare codice, è connettere una società in DB1 con un client in DB2, ottenere la loro lista prodotti da DB2 e INSERISCIla in DB1 se non è già lì. La prima volta dovrebbe essere un inventario completo. Ogni volta che viene eseguito lì dopo che nulla dovrebbe accadere a meno che un nuovo inventario è entrato nel feed durante la notte.

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




Se ottieni questo errore a causa di foreach e hai davvero bisogno di salvare prima un'entità all'interno del loop e utilizzare ulteriormente l'identità generata in loop, come nel mio caso, la soluzione più semplice è usare 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();
   }



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

La prima opzione è utilizzare un DBContext per generare gli oggetti elenco da 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 è ottenere un elenco di oggetti di database da DBContext, ma selezionare solo gli id. E poi scorrere l'elenco di id (presumibilmente un int) e ottenere l'oggetto corrispondente a ogni int e invocare SaveChanges () in questo modo. L'idea alla base di questo metodo è l'acquisizione di un ampio elenco di numeri interi, è molto più efficiente di ottenere un grande 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()
}



Sono molto in ritardo per la festa, ma oggi ho affrontato lo stesso errore e come ho risolto era semplice. Il mio scenario era simile a questo codice dato che stavo facendo transazioni DB all'interno di nested for-each loop.

Il problema è che una transazione con DB singolo richiede un po 'più di tempo rispetto a ogni ciclo, 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 facendo 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 ### //
                    }
                }



Avevo bisogno di leggere un enorme ResultSet e aggiornare alcuni record nella tabella. Ho provato ad usare pezzi come suggerito nella answer Drew Noakes .

Sfortunatamente dopo 50000 record ho OutofMemoryException. La risposta Entity framework set di dati di grandi dimensioni, fuori eccezione di memoria spiega che

EF crea una seconda copia dei dati che utilizza per il rilevamento delle modifiche (in modo che possa persistere le modifiche al database). EF detiene questo secondo set per tutta la durata del contesto e questo set ti sta facendo uscire di memoria.

La raccomandazione è di rinnovare il tuo contesto ogni lotto.

Quindi ho recuperato i valori Minimo e Massimo della chiave primaria - le tabelle hanno le chiavi primarie come numeri interi autoincrementali. Poi 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. Garantisce che l'utilizzo della memoria non aumenta.

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.




Basta inserire context.SaveChanges() dopo la fine del foreach (loop).




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



Usa sempre la tua selezione come Lista

Per esempio:

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

Quindi esegui il ciclo attraverso la raccolta mentre salvi le modifiche

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

             }



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 in questo modo:

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



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

Questo errore è dovuto a Entity Framework che crea una transazione implicita durante la chiamata SaveChanges (). Il modo migliore per aggirare l'errore consiste nell'utilizzare un modello diverso (ovvero, non salvare mentre si è nel mezzo della 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();
    }
} 



Come hai già identificato, non puoi salvare da una foreach che sta ancora pescando dal database tramite un lettore attivo.

Calling ToList() o ToArray() va bene per i piccoli set di dati, ma quando si hanno migliaia di righe, si consumerà 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++;
        }
    }
}

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

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

L'oggetto interrogabile su cui si chiama questo metodo deve essere ordinato. Questo perché Entity Framework supporta solo IQueryable<T>.Skip(int) su query ordinate, il che ha senso quando si considera che più query per intervalli diversi richiedono che l'ordine sia stabile. Se l'ordine 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 si desidera migliorare notevolmente il throughput, è consigliabile chiamare SaveChanges() meno frequentemente. Usa invece un codice come questo:

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

Ciò si traduce in 100 volte meno chiamate di aggiornamento del database. Ovviamente ognuna di queste chiamate richiede più tempo per essere completata, ma alla fine si esce sempre più avanti. Il tuo chilometraggio può variare, ma questo è stato il mondo più veloce per me.

E si aggira intorno all'eccezione che stavi vedendo.

EDIT Ho rivisitato questa domanda dopo aver eseguito SQL Profiler e aggiornato alcune cose per migliorare le prestazioni. Per chiunque sia interessato, ecco alcuni esempi di SQL che mostrano cosa 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 i 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



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

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

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




Cordiali saluti: da un libro e alcune linee corrette perché è valido:

Richiamando il metodo SaveChanges () inizia una transazione che ripristina automaticamente tutte le modifiche persistenti nel 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 entità piuttosto che dopo che l'iterazione è stata completata, specialmente quando stai aggiornando o eliminando un numero elevato di entità.

Se si tenta di richiamare SaveChanges () prima che tutti i dati siano stati elaborati, si incorre in una "Nuova transazione non consentita perché esistono 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 più record di record attivi (MARS) abilitati dalla stringa di connessione (la stringa di connessione predefinita di EF abilita MARS)

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




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

Ho scoperto che la mia query era errata e che c'erano oltre 250 edizioni in sospeso. Quindi ho corretto la mia richiesta e ora funziona correttamente.

Quindi, nella mia situazione: controlla la ricerca di errori, eseguendo il debug sul risultato restituito dalla query. Dopo quello correggi la domanda.

Spero che questo aiuti a risolvere i problemi futuri.




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

context.SaveChanges() metodo context.SaveChanges() funziona come un commit su un normale sistema di database (RDMS).

Basta apportare tutte le modifiche (che Entity Framework memorizza nella cache) e quindi salvarle 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.




Dopo aver tirato molti capelli, ho scoperto che i loop di foreach erano i colpevoli. Quello che deve succedere è chiamare EF, ma restituirlo in un IList<T> di quel tipo di destinazione, quindi eseguire il ciclo su 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;
    // ...
}



Perché non avere solo una proprietà Year , che va perfettamente bene?

Le interfacce non contengono campi poiché i campi rappresentano un'implementazione specifica della rappresentazione dei dati e l'esposizione di tali elementi interromperà l'incapsulamento. Quindi avere un'interfaccia con un campo sarebbe effettivamente codifica per un'implementazione anziché un'interfaccia, che è un curioso paradosso per un'interfaccia!

Ad esempio, parte delle specifiche del tuo Year potrebbe richiedere che non sia valido per ICar implementatori ICar consentire l'assegnazione ad un Year che è successivo all'anno corrente +1 o prima del 1900. Non c'è modo di dire che se hai esposto i campi Year - molto meglio usare le proprietà invece di fare il lavoro qui.





c# entity-framework transactions inversion-of-control