No momento, estou recebendo este erro:
System.Data.SqlClient.SqlException: nova transação não é permitida porque há outros threads em execução na sessão.
ao executar este 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 fica em um banco de dados 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 banco de dados em nosso Prod Server e é atualizado diariamente 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: isso é de outra pergunta que eu tive e que agora está respondida.
Nota: Ainda preciso fazer uma verificação isDeleted para que eu possa excluí-la do DB1 se ela saiu do inventário do cliente.
Tudo o que eu quero fazer, com esse código específico, é conectar uma empresa no DB1 a um cliente no DB2, obter sua lista de produtos no DB2 e INSERIR no DB1, se ainda não estiver lá. Na primeira vez, deve haver uma atração completa do inventário. Cada vez que é executado lá, nada deve acontecer, a menos que um novo inventário chegue ao feed durante a noite.
Então a grande questão - como resolver o erro de transação que estou recebendo? Preciso soltar e recriar meu contexto toda vez através dos loops (não faz sentido para mim)?
fonte
Respostas:
Depois de muito arrancar os cabelos, descobri que os
foreach
laços eram os culpados. O que precisa acontecer é chamar EF, mas devolvê-lo para umIList<T>
desse tipo de destino e depois fazer um loop noIList<T>
.Exemplo:
fonte
SaveChanges
enquanto ainda está obtendo resultados do banco de dados. Portanto, outra solução é apenas salvar as alterações assim que o loop for concluído.Como você já identificou, não é possível salvar de dentro de um
foreach
que ainda esteja sendo extraído do banco de dados por meio de um leitor ativo.Ligar
ToList()
ou nãoToArray()
é bom para pequenos conjuntos de dados, mas quando você tem milhares de linhas, estará consumindo uma grande quantidade de memória.É melhor carregar as linhas em pedaços.
Dado os métodos de extensão acima, você pode escrever sua consulta assim:
O objeto consultável no qual você chama esse método deve ser solicitado. Isso ocorre porque o Entity Framework suporta apenas
IQueryable<T>.Skip(int)
consultas ordenadas, o que faz sentido quando você considera que várias consultas para diferentes intervalos exigem que a ordem seja estável. Se a ordem não for importante para você, basta solicitar pela chave primária, pois é provável que tenha um índice em cluster.Esta versão consultará o banco de dados em lotes de 100. Observe que
SaveChanges()
é chamado para cada entidade.Se você deseja melhorar drasticamente sua taxa de transferência, ligue com
SaveChanges()
menos frequência. Use um código como este:Isso resulta em 100 vezes menos chamadas de atualização do banco de dados. É claro que cada uma dessas ligações leva mais tempo para ser concluída, mas você ainda sai à frente no final. Sua milhagem pode variar, mas isso foi um mundo mais rápido para mim.
E contorna a exceção que você estava vendo.
EDIT Eu revisitei essa pergunta depois de executar o SQL Profiler e atualizei algumas coisas para melhorar o desempenho. Para quem estiver interessado, aqui está um exemplo de SQL que mostra o que é criado pelo banco de dados.
O primeiro loop não precisa pular nada, por isso é mais simples.
As chamadas subseqüentes precisam ignorar trechos anteriores de resultados, para introduzir o uso de
row_number
:fonte
Publicamos agora uma resposta oficial ao bug aberto no Connect . As soluções alternativas que recomendamos são as seguintes:
Este erro ocorre devido ao Entity Framework criar uma transação implícita durante a chamada SaveChanges (). A melhor maneira de solucionar o erro é usar um padrão diferente (ou seja, não salvar enquanto estiver no meio da leitura) ou declarar explicitamente uma transação. Aqui estão três soluções possíveis:
fonte
Na verdade, você não pode salvar as alterações dentro de um
foreach
loop em C # usando o Entity Framework.context.SaveChanges()
O método atua como uma confirmação em um sistema de banco de dados regular (RDMS).Apenas faça todas as alterações (que o Entity Framework armazenará em cache) e salve todas elas de uma vez chamando
SaveChanges()
após o loop (fora dele), como um comando de confirmação do banco de dados.Isso funciona se você puder salvar todas as alterações de uma só vez.
fonte
Basta colocar
context.SaveChanges()
após o final do seuforeach
(loop).fonte
Sempre use sua seleção como lista
Por exemplo:
Em seguida, percorra a coleção enquanto salva as alterações
fonte
FYI: de um livro e algumas linhas ajustadas porque ainda é válida:
A chamada do método SaveChanges () inicia uma transação que reverte automaticamente todas as alterações persistidas no banco de dados se ocorrer uma exceção 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 você estiver atualizando ou excluindo um grande número de entidades.
Se você tentar chamar SaveChanges () antes de todos os dados terem sido processados, ocorrerá uma exceção "Nova transação não é permitida porque existem outros threads em execução na sessão". 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 o MARS (Multiple Active Record Sets) ativado pela string de conexão (a string de conexão padrão do EF habilita MARS)
Às vezes é melhor entender por que as coisas estão acontecendo ;-)
fonte
Tornar suas listas consultáveis em .ToList () e deve funcionar bem.
fonte
Eu estava recebendo esse mesmo problema, mas em uma situação diferente. Eu tinha uma lista de itens em uma caixa de listagem. O usuário pode clicar em um item e selecionar excluir, mas estou usando um processo armazenado para excluir o item, porque há muita lógica envolvida na exclusão do item. Quando eu chamo o processo armazenado, a exclusão funciona bem, mas qualquer chamada futura para SaveChanges causará o erro. Minha solução foi chamar o proc armazenado fora da EF e isso funcionou bem. Por alguma razão, quando eu chamo o proc armazenado usando a maneira EF de fazer as coisas, deixa algo em aberto.
fonte
SELECT
declaração no procedimento armazenado que produziu um conjunto de resultados vazio e, se esse conjunto de resultados não foi lido,SaveChanges
lançou essa exceção.Aqui estão outras 2 opções que permitem chamar SaveChanges () em um para cada loop.
A primeira opção é usar um DBContext para gerar os objetos da lista para iterar e criar um segundo DBContext para chamar SaveChanges (). Aqui está um exemplo:
A segunda opção é obter uma lista de objetos de banco de dados do DBContext, mas selecionar apenas os IDs. E, em seguida, percorra a lista de IDs (presumivelmente um int) e obtenha o objeto correspondente a cada int e invoque SaveChanges () dessa maneira. A idéia por trás desse método é pegar uma grande lista de números inteiros, é muito mais eficiente do que obter uma grande lista de objetos db e chamar .ToList () em todo o objeto. Aqui está um exemplo deste método:
fonte
Se você receber esse erro devido ao foreach e realmente precisar salvar uma entidade primeiro no loop interno e usar a identidade gerada ainda mais no loop, como foi no meu caso, a solução mais fácil é usar outro DBContext para inserir a entidade que retornará o ID e usará esse ID no contexto externo
Por exemplo
fonte
Assim, no projeto eram Eu tive esse mesmo problema exato o problema não estava no
foreach
ou o.toList()
que era realmente na configuração Autofac usamos. Isso criou algumas situações estranhas, onde o erro acima foi lançado, mas também vários outros erros equivalentes.Esta foi a nossa correção: Alterado isso:
Para:
fonte
Eu sei que é uma pergunta antiga, mas enfrentei esse erro hoje.
e descobri que, esse erro pode ser lançado quando um gatilho da tabela do banco de dados obtém um erro.
Para sua informação, você também pode verificar os gatilhos de suas tabelas quando receber esse erro.
fonte
Eu precisava ler um ResultSet enorme e atualizar alguns registros na tabela. Eu tentei usar pedaços como sugerido na Noakes tirei 's resposta .
Infelizmente, após 50000 registros, obtive OutofMemoryException. A resposta Entidade estrutura grande conjunto de dados, exceção de falta de memória , explica que
A recomendação é recriar seu contexto para cada lote.
Então, eu recuperei os valores Mínimo e Máximo da chave primária - as tabelas têm chaves primárias como números inteiros incrementais automáticos. Após o processamento, o contexto do bloco fecha e libera a memória. Ele garante que o uso da memória não esteja aumentando.
Abaixo está um trecho do meu código:
FromToRange é uma estrutura simples com propriedades De e Para.
fonte
Começamos a ver esse erro "Nova transação não é permitida porque há outros threads em execução na sessão" após a migração do EF5 para o EF6.
O Google nos trouxe aqui, mas não estamos ligando para
SaveChanges()
dentro do loop. Os erros foram gerados ao executar um procedimento armazenado usando o ObjectContext.ExecuteFunction dentro de uma leitura de loop foreach do banco de dados.Qualquer chamada para ObjectContext.ExecuteFunction agrupa a função em uma transação. Iniciar uma transação enquanto já existe um leitor aberto causa o erro.
É possível desativar a quebra do SP em uma transação, configurando a opção a seguir.
A
EnsureTransactionsForFunctionsAndCommands
opção permite que o SP seja executado sem criar sua própria transação e o erro não é mais gerado.Propriedade DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands
fonte
Eu também estava enfrentando o mesmo problema.
Aqui está a causa e a solução.
http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx
Antes de disparar 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 funções que lêem dados do banco de dados e retornam valores. Por exemplo, funções como isRecordExist.
Nesse caso, retornamos imediatamente da função se encontramos o registro e esquecemos de fechar o leitor.
fonte
O código abaixo funciona para mim:
fonte
No meu caso, o problema apareceu quando chamei Procedimento armazenado via EF e, em seguida, SaveChanges lança essa exceção. O problema estava em chamar o procedimento, o enumerador não foi descartado. Corrigi o código da seguinte maneira:
fonte
Estou muito atrasado para a festa, mas hoje enfrentei o mesmo erro e como resolvi era simples. Meu cenário era semelhante a esse código que eu estava fazendo transações de banco de dados dentro de loops aninhados para cada.
O problema é que uma transação de banco de dados único leva um pouco mais de tempo para cada loop, portanto, uma vez que a transação anterior não é concluída, a nova tração gera uma exceção. Portanto, a solução é criar um novo objeto no loop de cada onde você está fazendo uma transação de banco de dados.
Para os cenários mencionados acima, a solução será assim:
fonte
Estou um pouco atrasado, mas também tive esse erro. Resolvi o problema verificando quais eram os valores que estavam atualizando.
Descobri que minha consulta estava errada e que havia mais de 250 edições pendentes. Então, corrigi minha consulta e agora ela está correta.
Espero que isso ajude a resolver problemas futuros.
fonte