SqlException do Entity Framework – Nova transação não é permitida porque há outros encadeamentos em execução na session

Atualmente estou recebendo este erro:

System.Data.SqlClient.SqlException: Nova transação não é permitida porque há outros segmentos em execução na session.

durante a execução deste código:

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

Modelo # 1 – Este modelo está em um database em nosso servidor de desenvolvimento. Modelo # 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Modelo # 2 – Este modelo fica em um database no nosso Prod Server e é atualizado a cada dia por feeds automáticos. texto alternativo http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Nota – Os itens circulados em vermelho no Modelo # 1 são os campos que eu uso para “mapear” para o Modelo # 2. Por favor, ignore os círculos vermelhos no Modelo # 2: isto é de outra pergunta que eu tive que agora é respondida.

Nota: Eu ainda preciso colocar um cheque isDeleted para que eu possa excluí-lo do DB1 se ele tiver saído do estoque do nosso cliente.

Tudo o que quero fazer, com esse código específico, é conectar uma empresa no DB1 com um cliente no DB2, obter sua lista de produtos do DB2 e INSERT-lo no DB1, se ainda não estiver lá. A primeira vez deve ser um inventário completo do inventário. Cada vez que é executado lá, nada deve acontecer a menos que o novo inventário entre no feed durante a noite.

Então, a grande questão – como resolver o erro de transação que estou recebendo? Preciso largar e recriar meu contexto a cada vez através dos loops (não faz sentido para mim)?

Depois de muito puxar o cabelo, descobri que os loops foreach eram os culpados. O que precisa acontecer é chamar EF, mas retorná-lo para um IList desse tipo de alvo e então fazer um loop no IList .

Exemplo:

 IList 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; // ... } 

Como você já identificou, não é possível salvar a partir de um foreach que ainda esteja sendo extraído do database por meio de um leitor ativo.

Chamar ToList() ou ToArray() é bom para pequenos conjuntos de dados, mas quando você tem milhares de linhas, você estará consumindo uma grande quantidade de memory.

É melhor carregar as linhas em blocos.

 public static class EntityFrameworkUtil { public static IEnumerable QueryInChunksOf(this IQueryable queryable, int chunkSize) { return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk); } public static IEnumerable QueryChunksOfSize(this IQueryable 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++; } } } 

Dados os methods de extensão acima, você pode escrever sua consulta assim:

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

O object questionável chamado neste método deve ser solicitado. Isso ocorre porque o Entity Framework suporta somente IQueryable.Skip(int) em consultas ordenadas, o que faz sentido quando você considera que várias consultas para diferentes intervalos exigem que o ordenamento seja estável. Se a sorting não for importante para você, apenas faça o pedido pela chave primária, pois é provável que ela tenha um índice clusterizado.

Essa versão consultará o database em lotes de 100. Observe que SaveChanges() é chamado para cada entidade.

Se você quiser melhorar sua taxa de transferência drasticamente, deverá chamar SaveChanges() menos frequência. Use código como este em vez disso:

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

Isso resulta em 100 vezes menos chamadas de atualização do database. É claro que cada uma dessas chamadas leva mais tempo para ser concluída, mas você ainda fica muito à frente no final. Sua milhagem pode variar, mas isso foi muito mais rápido para mim.

E contorna a exceção que você estava vendo.

EDIT Eu revisitei esta questão depois de executar o SQL Profiler e atualizei algumas coisas para melhorar o desempenho. Para quem está interessado, aqui está um exemplo de SQL que mostra o que é criado pelo database.

O primeiro loop não precisa pular nada, então é mais simples.

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

Chamadas subseqüentes precisam pular blocos anteriores de resultados, portanto, introduz o uso 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 

Agora postamos uma resposta oficial ao bug aberto no Connect . As soluções alternativas recomendadas são as seguintes:

Esse erro é devido ao Entity Framework criar uma transação implícita durante a chamada SaveChanges (). A melhor maneira de contornar o erro é usar um padrão diferente (isto é, não salvar no meio da leitura) ou declarar explicitamente uma transação. Aqui estão três soluções possíveis:

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

Basta colocar context.SaveChanges() após o final do seu foreach (loop).

Eu estava recebendo esse mesmo problema, mas em uma situação diferente. Eu tinha uma lista de itens em uma checkbox de listview. O usuário pode clicar em um item e selecionar excluir, mas estou usando um procedimento armazenado para excluir o item, pois há muita lógica envolvida na exclusão do item. Quando eu chamo o procedimento armazenado a exclusão funciona bem, mas qualquer chamada futura para SaveChanges causará o erro. Minha solução foi chamar o procedimento armazenado fora da EF e isso funcionou bem. Por alguma razão, quando eu chamo o procedimento armazenado usando a maneira EF de fazer as coisas, deixa algo em aberto.

FYI: de um livro e algumas linhas ajustadas porque seu valor é válido:

Invocar o método SaveChanges () inicia uma transação que reverte automaticamente todas as alterações persistidas no database se uma exceção ocorrer antes da conclusão da iteração; caso contrário, a transação será confirmada. Você pode ser tentado a aplicar o método após cada atualização ou exclusão de entidade, e não após a conclusão da iteração, especialmente quando estiver atualizando ou excluindo um grande número de entidades.

Se você tentar chamar SaveChanges () antes que todos os dados tenham sido processados, você incorrerá em uma exceção “Nova transação não é permitida porque há outros encadeamentos em execução na session”. A exceção ocorre porque o SQL Server não permite iniciar uma nova transação em uma conexão que tenha um SqlDataReader aberto, mesmo com vários Active Record Sets (MARS) ativados pela cadeia de conexão (a cadeia de conexão padrão do EF ativa o MARS)

Às vezes é melhor entender por que as coisas estão acontecendo 😉

Aqui estão outras duas opções que permitem chamar SaveChanges () em um para cada loop.

A primeira opção é usar um DBContext para gerar seus objects de lista para iterar e criar um segundo DBContext para chamar SaveChanges (). Aqui está um exemplo:

 //Get your IQueryable list of objects from your main DBContext(db) IQueryable 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() } } 

A segunda opção é obter uma lista de objects de database do DBContext, mas selecionar apenas os ID’s. E então percorra a lista de id’s (presumivelmente um int) e obtenha o object correspondente a cada int, e invoque SaveChanges () dessa forma. A idéia por trás desse método é pegar uma grande lista de inteiros, é muito mais eficiente do que obter uma grande lista de objects db e chamar .ToList () em todo o object. Aqui está um exemplo desse método:

 //Get the list of objects you want from your DBContext, and select just the Id's and create a list List 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() } 

Use sempre sua seleção como lista

Por exemplo:

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

Em seguida, percorra a coleção enquanto salva as alterações

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

Na verdade, você não pode salvar as alterações em um loop foreach em C # usando o Entity Framework.

context.SaveChanges() método context.SaveChanges() age como um commit em um sistema de database regular (RDMS).

Basta fazer todas as alterações (que o Entity Framework armazenará em cache) e, em seguida, salvá-las todas de uma vez chamando SaveChanges() após o loop (fora dele), como um comando de confirmação do database.

Isso funciona se você puder salvar todas as alterações de uma só vez.

Então, no projeto onde tive exatamente o mesmo problema, o problema não estava no foreach ou no .toList() , na verdade, estava na configuração do AutoFac que usamos. Isso criou algumas situações estranhas onde o erro acima foi lançado, mas também um monte de outros erros equivalentes foram lançados.

Esta foi a nossa correção: mudou isso:

 container.RegisterType().As().InstancePerLifetimeScope(); container.RegisterType().As().SingleInstance(); container.RegisterType().As().InstancePerRequest(); 

Para:

 container.RegisterType().As().As(); container.RegisterType().As().As().InstancePerLifetimeScope(); container.RegisterType().As().As();//.InstancePerRequest(); 

Eu também estava enfrentando o mesmo problema.

Aqui está a causa e solução.

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

Certifique-se antes de triggersr comandos de manipulação de dados como inserções, atualizações, você fechou todos os leitores SQL ativos anteriores.

O erro mais comum são as funções que leem dados do database e retornam valores. Por exemplo, funções como isRecordExist.

Nesse caso, retornamos imediatamente da function se encontrarmos o registro e esquecermos de fechar o leitor.

No meu caso, o problema apareceu quando eu chamei Stored Procedure via EF e depois SaveChanges lançar essa exceção. O problema estava em chamar o procedimento, o enumerador não foi descartado. Eu fixei o código da seguinte maneira:

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

Eu precisava ler um ResultSet enorme e atualizar alguns registros na tabela. Eu tentei usar pedaços como sugerido na resposta de Drew Noakes .

Infelizmente, após 50000 registros, recebi OutofMemoryException. A resposta Conjunto de dados grande da estrutura de entidades, exceção de memory explica, que

O EF cria uma segunda cópia de dados que utiliza para detecção de alterações (para que possa persistir alterações no database). O EF mantém este segundo conjunto para o tempo de vida do contexto e este é o conjunto que o deixa sem memory.

A recomendação é renovar seu contexto a cada lote.

Por isso, recuperei os valores Mínimo e Máximo da chave primária – as tabelas têm chaves primárias como inteiros incrementais automáticos. Depois, recupero dos fragments de database dos registros abrindo o contexto para cada fragment. Depois de processar o contexto do bloco, fecha e libera a memory. Ele garante que o uso de memory não está crescendo.

Abaixo está um trecho do meu código:

  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 GetminMaxIds() { var minMaxIds = new FromToRange(); 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 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 é uma estrutura simples com as propriedades From e To.

O código abaixo funciona para mim:

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

Estou muito atrasado para a festa, mas hoje enfrentei o mesmo erro e como resolvi que era simples. Meu cenário era semelhante a este código dado que eu estava fazendo transactions de database dentro de loops nesteds para cada um.

O problema é que uma transação de database único leva um pouco mais de tempo do que o loop each, portanto, assim que a transação anterior não estiver concluída, a nova geração lançará uma exceção, então a solução é criar um novo object no loop for-each onde você está fazendo uma transação do database.

Para os cenários mencionados acima, a solução será assim:

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

Estou um pouco atrasado, mas também tive esse erro. Eu resolvi o problema, verificando quais são os valores que estão sendo atualizados.

Descobri que minha consulta estava errada e que há mais de 250 edições pendentes. Então eu corrigi minha consulta e agora funciona corretamente.

Então, na minha situação: Verifique a consulta de erros, depurando sobre o resultado que a consulta retorna. Depois disso, corrija a consulta.

Espero que isso ajude a resolver problemas futuros.

Eu sei que é uma pergunta antiga, mas enfrentei esse erro hoje.

e eu achei que, este erro pode ser lançado quando um gatilho de tabela de database recebe um erro.

Para sua informação, você pode verificar seus gatilhos de tabelas também quando você receber esse erro.

Se você receber este erro devido a foreach e você realmente precisa salvar uma entidade primeiro no loop interno e usar a identidade gerada em loop, como no meu caso, a solução mais fácil é usar outro DBContext para inserir a entidade que retornará o ID e use este ID no contexto externo

Por exemplo

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

Fazendo suas listas questionáveis ​​para .ToList () e deve funcionar bem.