C # Entity Framework: problema de memória de entrada de extensões em massa

Aug 17 2020

Atualmente estou usando extensões EF. Uma coisa que não entendo, "supostamente ajuda no desempenho"

no entanto, colocar mais de um milhão de registros na variável List é um problema de memória em si. Portanto, se quiser atualizar milhões de registros, sem manter tudo na memória, como isso pode ser feito de forma eficiente?

Devemos usar um for loope atualizar em lotes, digamos 10.000? O EFExtensions BulkUpdate tem alguma funcionalidade nativa para oferecer suporte a isso?

Exemplo:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics');  // this creates IQueryable

await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');

_dbContext.BulkUpdateAsync(productUpdate.ToList());

Recurso:

https://entityframework-extensions.net/bulk-update

Respostas

Pac0 Aug 18 2020 at 00:27

Achei a maneira "adequada" das extensões EF de fazer uma atualização em massa com uma condição semelhante a uma consulta:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics')
    .UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });

Isso deve resultar em um SQL adequado UPDATE ... SET ... WHERE, sem a necessidade de carregar entidades primeiro, conforme a documentação :

Por que UpdateFromQueryé mais rápido do que SaveChanges, BulkSaveChangese BulkUpdate?

UpdateFromQueryexecuta uma instrução diretamente no SQL, como UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key].

Outras operações normalmente requerem uma ou várias viagens de ida e volta do banco de dados, o que torna o desempenho mais lento.

Você pode verificar a sintaxe de trabalho neste exemplo de violino dotnet , adaptado de seu exemplo de BulkUpdate.

Outras considerações

  • Nenhuma menção de operações em lote para isso, infelizmente.

  • Antes de fazer uma grande atualização como esta, pode valer a pena considerar a desativação dos índices que você pode ter nesta coluna e reconstruí-los depois. Isso é especialmente útil se você tiver muitos deles.

  • Cuidado com a condição no Where, se não puder ser traduzido como SQL pelo EF, então será feito do lado do cliente, significando a "usual" terrível viagem de ida e volta "Carregar - alterar na memória - atualizar"

2 Flater Aug 17 2020 at 23:43

Isso é algo para o qual EF não foi feito. As interações do banco de dados da EF começam no objeto de registro e fluem a partir daí. EF não pode gerar um UPDATE parcial (ou seja, não sobrescrever tudo) se a entidade não foi alterada (e, portanto, carregada) e, da mesma forma, não pode DELETE registros com base em uma condição em vez de uma chave.

Não há EF equivalente (sem carregar todos esses registros) para a lógica de atualização / exclusão condicional, como

UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'

ou

DELETE FROM People
WHERE FirstName = 'Robert'

Fazer isso usando a abordagem EF exigirá que você carregue todas essas entidades apenas para enviá-las de volta (com uma atualização ou exclusão) para o banco de dados, e isso é um desperdício de largura de banda e desempenho como você já descobriu.

A melhor solução que encontrei aqui é ignorar os métodos amigáveis ​​ao LINQ do EF e, em vez disso, executar você mesmo o SQL bruto. Isso ainda pode ser feito usando um contexto EF.

using (var ctx = new MyContext())
{
    string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
    int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);

    string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
    int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}

Mais informações aqui . Claro, não se esqueça de se proteger contra injeção de SQL onde for relevante.

A sintaxe específica para executar o SQL bruto pode variar de acordo com a versão do EF / EF Core, mas, tanto quanto eu sei, todas as versões permitem que você execute o SQL bruto.


Não posso comentar sobre o desempenho das Extensões EF ou BulkUpdate especificamente, e não vou comprá-los deles.

Com base em sua documentação, eles não parecem ter os métodos com as assinaturas corretas para permitir a lógica de atualização / exclusão condicional.

  • BulkUpdate não parece permitir que você insira a condição lógica (o WHERE no seu comando UPDATE) que permitiria a você otimizar isso.
  • BulkDeleteainda tem uma BatchSizeconfiguração, o que sugere que eles ainda estão lidando com os registros um por vez (bem, por lote, eu acho), e não usando uma única consulta DELETE com uma condição (cláusula WHERE).

Com base no código pretendido na pergunta, as Extensões EF não estão realmente fornecendo o que você precisa. É mais eficiente e barato simplesmente executar SQL bruto no banco de dados, pois isso ignora a necessidade do EF de carregar suas entidades.

Atualização
que posso ter corrigido, há algum suporte para a lógica de atualização condicional, como visto aqui . No entanto, não está claro para mim enquanto o exemplo ainda carrega tudo na memória e qual é o propósito dessa lógica WHERE condicional se você já carregou tudo na memória (por que não usar LINQ na memória então?)

No entanto, mesmo que funcione sem carregar as entidades, ainda é:

  • mais limitado (apenas verificações de igualdade são permitidas, em comparação com SQL permitindo qualquer condição booleana que seja SQL válida),
  • relativamente complexo (não gosto de sua sintaxe, talvez seja subjetiva)
  • e mais caro (ainda uma biblioteca paga)

em comparação com a execução de sua própria consulta SQL bruta. Eu ainda sugeriria rolar seu próprio SQL bruto aqui, mas isso é apenas minha opinião.