EF Core 7 - Os novos recursos ExecuteDelete e ExecuteUpdate - I
Neste artigo vou apresentar os novos recursos ExecuteDelete para executar a exclusão de grandes quantidades de dados e ExecuteUpdate para atualizar uma grande quantidade de dados. |
Por padrão, o EF Core rastreia alterações em entidades e, em seguida, envia atualizações para o banco de dados quando um dos métodos SaveChanges é chamado
As alterações são
enviadas apenas para propriedades e relacionamentos que realmente foram
alterados. Além disso, as entidades rastreadas permanecem sincronizadas
com as alterações enviadas ao banco de dados. Esse mecanismo é uma maneira
eficiente e conveniente de enviar inserções, atualizações e exclusões de
uso geral para o banco de dados. Essas alterações também são agrupadas para
reduzir o número de idas e vindas do banco de dados.
Entretanto, às vezes é útil executar comandos de atualização ou exclusão
no banco de dados sem envolver o rastreador de alterações.
O EF7 permite isso com os novos métodos ExecuteUpdate e ExecuteDelete.
Esses métodos são aplicados a uma consulta LINQ e atualizarão ou excluirão entidades no banco de dados com base nos resultados dessa consulta. Muitas entidades podem ser atualizadas com um único comando e as entidades não são carregadas na memória, o que significa que isso pode resultar em atualizações e exclusões mais eficientes.
Assim, ao invés de primeiro recuperar as entidades e ter todas as entidades na memória antes de podermos executar uma ação sobre elas e, por último, confirmá-las no SQL, agora podemos fazer isso com apenas uma única operação, que resulta em um comando SQL.
No entanto existem alguns detalhes nos quais você deve ficar atento :
Dessa forma os novos métodos ExecuteUpdate e ExecuteDelete vieram para complementar o mecanismo SaveChanges existente e não para substituí-lo.
Vamos iniciar apresentando o novo método ExecuteDelete.
Para isso vamos criar uma aplicação Console usando o .NET 7.0 no VS 2022 (17.4.0) e o SQL Server.
recursos usados:
Criando o projeto Console
Abra o VS 2022 e crie um novo projeto do tipo console chamado EF7ExecuteDelete.
Após criar o projeto inclua as referências aos pacotes :
A seguir crie duas pastas no projeto : Entities e Data
Na pasta Entities crie as classes Cliente, Endereco e Animal :
1- Cliente
public
class
Aluno { public int ClienteId { get; set; } public string Nome { get; set; } = ""; public string Email { get; set; } = ""; public Endereco? Endereco { get; set; } public List<Animal> Animais { get; set; } = new List<Animal>(); } |
2- Endereco
//owned entity public class Endereco { public int EnderecoId { get; set; } public string Local { get; set; } = null!; } |
3- Animal
//owned entity public class Animal { public long AnimalId { get; set; } public string Nome { get; set; } = string.Empty; public string Raca { get; set; } = string.Empty; } |
Na pasta Data vamos criar a classe AppDbContext que é a classe de contexto onde definimos o mapeamento ORM.
1- AppDbContext
using
EF7ExecuteDelete.Entities; using Microsoft.EntityFrameworkCore; namespace EF7ExecuteDelete.Data; public class AppDbContext : DbContext{ public DbSet<Cliente> Clientes { get; set; } public DbSet<Animal> Animais { get; set; } public DbSet<Endereco> Enderecos { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer("Data
Source=.;Initial Catalog=PetsDB;Integrated
//shadow property |
Ao definir a string de conexão indicamos o nome do banco de dados a ser criado como PetsDB e também definimos na string de conexão a propriedade TrustServerCertificate=True para ignorar os mecanismos normais de segurança.
Isso é necessário pois com o .NET 7.0 houve uma alteração no pacote Microsoft.Data.SqlClient e agora a string de conexão para o SqlClient utiliza o valor Encrypt=True e com isso o servidor precisa ser configurado com um certificado válido e o cliente precisa confiar neste certificado.
Se essas condições não forem atendidas teremos uma SqlException lançada quando a conexão for acionada. Uma outra forma de mitigar o problema é definir explicitamente Encrypt=False na string de conexão.
Lembrando que ao adotar esses procedimentos em um ambiente de produção estamos colocando o servidor em um estado inseguro o que não é recomendável.
No método OnModelCreating, que é chamado pelo framework quando nosso contexto for criado pela primeira vez para construir o modelo e seu mapeamento na memória, vamos realizar o mapeamento definindo uma Shadow Property de forma a mapear Endereco e Animal para ter como chave estrangeira ClienteId.
Aplicando o Migrations usando os comandos :
Teremos a criação do banco de dados PetsDB e dsa tabelas Clientes, Enderecos e Animais com a seguinte estrutura:
Para poder incluir dados nas tabelas vamos criar na pasta Data a classe SeedDatabase e o método estático PopulaDB() :
public class SeedDatabase { public static void PopulaDB(AppDbContext context) { context.Database.EnsureDeleted(); context.Database.EnsureCreated();
context.Clientes.AddRange(Enumerable.Range(1, 1_000).Select(i => |
Neste código estamos usando o método EnsureDeleted que garante que o banco de dados para o contexto não exista.
O método EnsureCreated() que garante que o banco de dados para o contexto exista.
A seguir usamos o método AddRange que anexa uma coleção de entidades Cliente, Endereco e Animal ao contexto com o estado Added (Adicionado).
Também usamos o método Enumerable.Range() que gera uma sequência de números dentro de um intervalo especificado. Para isso ele usa o método estático Range() da classe Enumerable.
A sintaxe usada é :
Enumerable.Range (int start, int count);
start - indica o início da sequência
count - indica quantos elementos serão gerados;
O retorno é um IEnumerable<int> que contém a sequência gerada. Lembre-se de que cada número subsequente é incrementado em 1, o que também significa que os elementos estão em ordem crescente.
Para o nosso exemplo estamos incluindo 1000 registros nas tabelas Clientes e Endereços e 3000 registros na tabela Animais.
Testando o ExecuteDelete
Para testar o método ExecuteDelete vamos incluir o código abaixo na classe Program.
1- Primeiro vamos incluir dados na tabela chamando o método PopulaDB() :
using
(var
context = new
AppDbContext()) { Console.WriteLine("\nPopulando as tabelas..."); SeedDatabase.PopulaDB(context); var clientes = context.Clientes.Count(); var animais = context.Animais.Count(); var enderecos = context.Enderecos.Count(); Console.WriteLine($"\nExistem {clientes} clientes"); Console.WriteLine($"Existem {animais} animais"); Console.WriteLine($"Existem {enderecos} endereços"); } |
Executando este código terermos o resultado abaixo:
Teremos assim os dados em cada tabela e agora podemos testar a exclusão de animais usando o ExecuteDelete.
Para isso vamos executar o código abaixo na classe Program:
using
(var
context = new
AppDbContext()) { var registros = context.Animais.Count(); Console.WriteLine($"\nExistem {registros} de animais na tabela\n"); Console.WriteLine("\nDeletando Animais..."); var animaisExcluidos = context.Animais .Where(p => p.Nome.Contains("1")) .ExecuteDelete(); Console.WriteLine( $"\n{animaisExcluidos} animais foram excluidos da tabela...");} |
A consulta LINQ usada é a seguinte:
var
animaisExcluidos = context.Animais
.Where(p => p.Nome.Contains("1"))
.ExecuteDelete();
Note que temos que definir uma condição de filtro para usar o ExecuteDelete.
Observe que também podemos obter o número de linhas afetadas pela exclusão.
O resultado da execução é dada a seguir:
Como você pode ver, o EF Core simplesmente gera uma instrução SQL para excluir as entidades que correspondem à condição. As entidades também não são mais mantidas na memória. Lindo, simples e eficiente!
Vamos agora realizar uma exclusão em cascata.
Vamos excluir agora os clientes usando ExecuteDelete, e , isso vai excluir em cascata os registros de Enderecos e dos Animais relacionados.
Para isso vamos executar o código abaixo na classe Program:
using
(var
context = new
AppDbContext()) { var registros = context.Clientes.Count(); Console.WriteLine($"\nExistem {registros} de clientes na tabela\n"); Console.WriteLine( "\nDeletando Clientes (em cascata)..."); var
clientesDeletados = context.Clientes Console.WriteLine($"\n{clientesDeletados} clientes foram excluidos da tabela...");
var
enderecos = context.Enderecos.Count();
var
animais = context.Animais.Count(); |
Antes de executar vamos popular novamente as tabelas do banco de dados visto que já excluimos os animais. Após isso podemos executar e obter o seguinte resultado:
Os métodos ExecuteUpdate e ExecuteDelete só podem atuar em uma única tabela. Isso tem implicações ao trabalhar com diferentes estratégias de mapeamento de herança. Geralmente, não há problemas ao usar a estratégia de mapeamento TPH, pois há apenas uma tabela para modificar.
Além disso , como já foi mencionado, pode ser necessário excluir ou atualizar entidades dependentes antes que o principal de um relacionamento possa ser excluído. Por exemplo, cada Post é dependente de seu Autor associado. Isso significa que um Autor não pode ser excluído se uma postagem ainda fizer referência a ele; isso violará a restrição de chave estrangeira no banco de dados.
Considere essas limitações ao usar este recurso.
Na
próxima parte
do artigo iremos apresentar o método ExecuteUpdate.
E estamos
conversados...
Pegue o projeto aqui : EF7ExecuteDelete.zip
"Eu te
invoquei, ó Deus, pois me queres ouvir; inclina para mim os teus ouvidos, e
escuta as minhas palavras.
Faze maravilhosas as tuas beneficências, ó tu que livras aqueles que em ti
confiam dos que se levantam contra a tua destra."
Salmos 17:6,7
Referências: