Estou procurando a maneira mais rápida de inserir no Entity Framework.
Estou perguntando isso por causa do cenário em que você tem um TransactionScope ativo e a inserção é enorme (mais de 4000). Pode potencialmente durar mais de 10 minutos (tempo limite padrão das transações) e isso levará a uma transação incompleta.
c#
sql
entity-framework
Bongo Sharp
fonte
fonte
Respostas:
À sua observação nos comentários à sua pergunta:
Essa é a pior coisa que você pode fazer! A chamada
SaveChanges()
para cada registro diminui extremamente as inserções em massa. Eu faria alguns testes simples que provavelmente melhorarão o desempenho:SaveChanges()
uma vez após TODOS os registros.SaveChanges()
depois, por exemplo, 100 registros.SaveChanges()
depois, por exemplo, 100 registros e descarte o contexto e crie um novo.Para inserções em massa, estou trabalhando e experimentando um padrão como este:
Eu tenho um programa de teste que insere 560.000 entidades (9 propriedades escalares, sem propriedades de navegação) no banco de dados. Com esse código, ele funciona em menos de 3 minutos.
Para o desempenho, é importante chamar
SaveChanges()
depois de "muitos" registros ("muitos" em torno de 100 ou 1000). Também melhora o desempenho para descartar o contexto após SaveChanges e criar um novo. Isso limpa o contexto de todas as entidades,SaveChanges
não faz isso, as entidades ainda estão anexadas ao contexto no estadoUnchanged
. É o tamanho crescente das entidades anexadas no contexto que retarda a inserção passo a passo. Portanto, é útil limpá-lo depois de algum tempo.Aqui estão algumas medidas para minhas 560000 entidades:
O comportamento no primeiro teste acima é que o desempenho é muito não linear e diminui extremamente ao longo do tempo. ("Muitas horas" é uma estimativa, eu nunca terminei este teste, parei em 50.000 entidades após 20 minutos.) Esse comportamento não linear não é tão significativo em todos os outros testes.
fonte
AutoDetectChangesEnabled = false;
o DbContext. Ele também tem um grande efeito de desempenho adicional: stackoverflow.com/questions/5943394/…DbContext
, NOTObjectContext
?Essa combinação aumenta a velocidade suficientemente bem.
fonte
A maneira mais rápida seria usar a extensão de pastilhas em massa , que desenvolvi
nota: este é um produto comercial, gratuito
Ele usa SqlBulkCopy e datareader personalizado para obter desempenho máximo. Como resultado, é 20 vezes mais rápido que o uso de inserção regular ou AddRange
o uso é extremamente simples
fonte
Você deve usar o
System.Data.SqlClient.SqlBulkCopy
para isso. Aqui está a documentação e, é claro, existem muitos tutoriais online.Desculpe, eu sei que você estava procurando uma resposta simples para que a EF fizesse o que você deseja, mas as operações em massa não são realmente para o que os ORMs são destinados.
fonte
Eu concordo com Adam Rackis.
SqlBulkCopy
é a maneira mais rápida de transferir registros em massa de uma fonte de dados para outra. Usei isso para copiar 20 mil registros e levou menos de 3 segundos. Veja o exemplo abaixo.fonte
AsDataReader()
método de extensão, explicado nesta resposta: stackoverflow.com/a/36817205/1507899Eu recomendaria este artigo sobre como fazer inserções em massa usando EF.
Entity Framework e INSERTs em massa lenta
Ele explora essas áreas e compara o desempenho:
fonte
como nunca foi mencionado aqui, quero recomendar EFCore.BulkExtensions aqui
fonte
Eu investiguei a resposta de Slauma (o que é incrível, obrigado pela ideia) e reduzi o tamanho do lote até atingir a velocidade ideal. Olhando para os resultados do Slauma:
É visível que há aumento de velocidade ao passar de 1 para 10 e de 10 para 100, mas de 100 para 1000 a velocidade de inserção está caindo novamente.
Portanto, concentrei-me no que está acontecendo quando você reduz o tamanho do lote para um valor entre 10 e 100, e aqui estão meus resultados (estou usando diferentes conteúdos de linha, portanto, meus tempos são de valor diferente):
Com base nos meus resultados, o valor real ideal é de cerca de 30 para o tamanho do lote. É menor que 10 e 100. O problema é que não tenho idéia do porquê de 30 ser o ideal, nem poderia ter encontrado uma explicação lógica para isso.
fonte
Como outras pessoas disseram, SqlBulkCopy é a maneira de fazer isso, se você deseja realmente um bom desempenho de pastilha.
É um pouco complicado de implementar, mas existem bibliotecas que podem ajudá-lo. Existem alguns por aí, mas vou descaradamente conectar minha própria biblioteca neste momento: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
O único código que você precisa é:
Então, quanto mais rápido é? É muito difícil dizer, porque depende de muitos fatores, desempenho do computador, rede, tamanho do objeto, etc. Os testes de desempenho sugeridos sugerem que 25k entidades podem ser inseridas a cerca de 10s da maneira padrão no host local, se você otimizar sua configuração EF como mencionado nas outras respostas. Com EFUtilities que leva cerca de 300ms. Ainda mais interessante é que salvei cerca de 3 milhões de entidades em menos de 15 segundos usando esse método, com média de cerca de 200 mil entidades por segundo.
O único problema é claro se você precisar inserir dados repetidos. Isso pode ser feito com eficiência no servidor sql usando o método acima, mas requer que você tenha uma estratégia de geração de ID que permita gerar IDs no código do aplicativo para o pai, para que você possa definir as chaves estrangeiras. Isso pode ser feito usando GUIDs ou algo como a geração de ID do HiLo.
fonte
EFBatchOperation
tivesse um construtor para o qual você passa noDbContext
lugar de todo método estático. Versões genéricasInsertAll
eUpdateAll
que automaticamente acham a coleção, semelhante aDbContext.Set<T>
, também seriam boas.Dispose()
O contexto cria problemas se as entidades em que vocêAdd()
confia em outras entidades pré-carregadas (por exemplo, propriedades de navegação) no contextoUso conceitos semelhantes para manter meu contexto pequeno e obter o mesmo desempenho
Mas, em vez do
Dispose()
contexto e recriar, simplesmente desanexo as entidades que jáSaveChanges()
envolva-o com try catch e,
TrasactionScope()
se necessário, não os mostre aqui para manter o código limpofonte
Eu sei que essa é uma pergunta muito antiga, mas um cara aqui disse que desenvolveu um método de extensão para usar a inserção em massa com EF e, quando verifiquei, descobri que a biblioteca custa US $ 599 hoje (para um desenvolvedor). Talvez faça sentido para toda a biblioteca, no entanto, para apenas a inserção em massa, isso é demais.
Aqui está um método de extensão muito simples que eu criei. Eu uso isso em par com o banco de dados primeiro (não testei com o código primeiro, mas acho que funciona da mesma forma). Mude
YourEntities
com o nome do seu contexto:Você pode usá-lo em qualquer coleção que herda
IEnumerable
, assim:fonte
await bulkCopy.WriteToServerAsync(table);
Tente usar um procedimento armazenado que obterá um XML dos dados que você deseja inserir.
fonte
Eu fiz uma extensão genérica do exemplo de @Slauma acima;
Uso:
fonte
Existem algumas bibliotecas de terceiros compatíveis com o Bulk Insert disponíveis:
Consulte: Biblioteca de inserção em massa do Entity Framework
Tenha cuidado ao escolher uma biblioteca de inserção em massa. Somente o Entity Framework Extensions suporta todos os tipos de associações e heranças e é o único ainda suportado.
Isenção de responsabilidade : sou o proprietário do Entity Framework Extensions
Esta biblioteca permite que você execute todas as operações em massa necessárias para seus cenários:
Exemplo
fonte
Use
SqlBulkCopy
:fonte
Uma das maneiras mais rápidas de salvar uma lista, você deve aplicar o seguinte código
AutoDetectChangesEnabled = false
Add, AddRange & SaveChanges: Não detecta alterações.
ValidateOnSaveEnabled = false;
Não detecta o rastreador de alterações
Você deve adicionar pepitas
Agora você pode usar o seguinte código
fonte
SqlBulkCopy é super rápido
Esta é a minha implementação:
fonte
[Atualização 2019] EF Core 3.1
Seguindo o que foi dito acima, a desativação do AutoDetectChangesEnabled no EF Core funcionou perfeitamente: o tempo de inserção foi dividido por 100 (de muitos minutos a alguns segundos, 10k registros com relacionamentos entre tabelas)
O código atualizado é:
fonte
Aqui está uma comparação de desempenho entre o uso do Entity Framework e o uso da classe SqlBulkCopy em um exemplo realista: Como inserir objetos complexos em massa no banco de dados do SQL Server
Como outros já enfatizaram, os ORMs não devem ser usados em operações em massa. Eles oferecem flexibilidade, separação de preocupações e outros benefícios, mas as operações em massa (exceto a leitura em massa) não são uma delas.
fonte
Outra opção é usar o SqlBulkTools disponível no Nuget. É muito fácil de usar e possui alguns recursos poderosos.
Exemplo:
Consulte a documentação para obter mais exemplos e uso avançado. Isenção de responsabilidade: sou o autor desta biblioteca e qualquer opinião é de minha opinião.
fonte
Como por meu conhecimento existe
no BulkInsert
emEntityFramework
aumentar o desempenho dos enormes inserções.Neste cenário você pode ir com SqlBulkCopy em
ADO.net
resolver o seu problemafonte
WriteToServer
que leva aDataTable
.Você já tentou inserir através de um trabalhador ou tarefa em segundo plano?
No meu caso, estou inserindo 7760 registradores, distribuídos em 182 tabelas diferentes com relacionamentos de chave estrangeira (por NavigationProperties).
Sem a tarefa, demorou 2 minutos e meio. Dentro de uma tarefa (
Task.Factory.StartNew(...)
), levou 15 segundos.Estou apenas fazendo o
SaveChanges()
depois de adicionar todas as entidades ao contexto. (para garantir a integridade dos dados)fonte
Todas as soluções escritas aqui não ajudam, porque quando você faz SaveChanges (), as instruções de inserção são enviadas ao banco de dados uma a uma, é assim que a Entity funciona.
E se sua viagem ao banco de dados e ao retorno for de 50 ms, por exemplo, o tempo necessário para inserção será o número de registros x 50 ms.
Você precisa usar o BulkInsert, aqui está o link: https://efbulkinsert.codeplex.com/
O tempo de inserção foi reduzido de 5-6 minutos para 10-12 segundos usando-o.
fonte
Você pode usar a biblioteca de pacotes em massa . A versão de inserção em massa 1.0.0 é usada em projetos com estrutura de entidade> = 6.0.0.
Mais descrição pode ser encontrada aqui - código-fonte da operação em massa
fonte
[NOVA SOLUÇÃO PARA POSTGRESQL] Ei, eu sei que é um post bastante antigo, mas recentemente encontrei um problema semelhante, mas estávamos usando o Postgresql. Eu queria usar um bulkinsert eficaz, o que acabou sendo bastante difícil. Não encontrei nenhuma biblioteca gratuita adequada para fazer isso neste banco de dados. Eu encontrei apenas esse ajudante: https://bytefish.de/blog/postgresql_bulk_insert/, que também está no Nuget. Eu escrevi um pequeno mapeador, que mapeou automaticamente as propriedades da maneira como o Entity Framework:
Eu o uso da seguinte maneira (eu tinha uma entidade chamada Empresa):
Eu mostrei um exemplo com transação, mas também pode ser feito com a conexão normal recuperada do contexto. compromkingsToAdd é enumerável de registros normais de entidade, que eu desejo inserir em massa no DB.
Esta solução, para a qual eu tenho depois de algumas horas de pesquisa e tentativa, é como você poderia esperar muito mais rápido e, finalmente, fácil de usar e grátis! Eu realmente aconselho você a usar esta solução, não apenas pelos motivos mencionados acima, mas também porque é a única com a qual não tive problemas com o próprio Postgresql, muitas outras soluções funcionam perfeitamente, por exemplo, com o SqlServer.
fonte
O segredo é inserir em uma tabela de preparação em branco idêntica. As inserções são muito rápidas. Em seguida, execute uma única inserção disso na sua tabela principal principal. Em seguida, trunque a tabela de preparo pronta para o próximo lote.
ie
fonte
Mas, para mais de (+4000) inserções, recomendo usar o procedimento armazenado. anexado o tempo decorrido. Eu inseri 11.788 linhas em 20 "
é isso código
fonte
Use o procedimento armazenado que recebe dados de entrada em forma de xml para inserir dados.
A partir do seu código c #, insira os dados como xml.
por exemplo, em c #, a sintaxe seria assim:
fonte
Use esta técnica para aumentar a velocidade de inserção de registros no Entity Framework. Aqui eu uso um procedimento armazenado simples para inserir os registros. E para executar esse procedimento armazenado, uso o método .FromSql () do Entity Framework que executa o SQL bruto.
O código do procedimento armazenado:
Em seguida, faça um loop em todos os seus registros 4000 e adicione o código do Entity Framework que executa os
O procedimento é iniciado a cada 100º loop.
Para isso, crio uma consulta de string para executar este procedimento, continue anexando a ele todos os conjuntos de registros.
Em seguida, verifique se o loop está sendo executado nos múltiplos de 100 e, nesse caso, execute-o usando
.FromSql()
.Verifique o código abaixo:
fonte