Quando desenvolvemos aplicações que lidam com dados sensíveis ou que precisam manter um histórico de registros, a exclusão física de dados pode não ser a melhor abordagem. Nesses casos, o Soft Delete se apresenta como uma solução elegante e eficaz. Neste artigo, exploraremos o que é o Soft Delete, como implementá-lo usando o Entity Framework e exemplos práticos para ilustrar seu funcionamento.
O que é Soft Delete?
O Soft Delete é uma técnica de gestão de dados que envolve marcar registros como “excluídos” de forma lógica, em vez de removê-los fisicamente do banco de dados. Ou seja, ao invés de deletar permanentemente, você utiliza uma marcação para indicar que o registro não está mais ativo, mas ainda existe no sistema. Isso é frequentemente feito através de uma coluna booleana, como “IsDeleted” ou “Deleted”, que indica o status do registro.
Utilidade do Soft Delete
A utilização do Soft Delete oferece diversas vantagens:
Recuperação de dados: permite restaurar dados excluídos acidentalmente.
Histórico de operações: mantém um registro de exclusões, o que pode ser útil para auditorias e rastreamento de atividades.
Integridade referencial: ajuda a manter a integridade referencial do banco de dados, evitando problemas de chaves estrangeiras quebradas.
Exclusão tradicional
A exclusão tradicional remove fisicamente o registro do banco de dados, de modo que ele não pode ser recuperado posteriormente. Esta abordagem é simples e direta, mas não é adequada para cenários em que a recuperação de dados excluídos ou a manutenção de um histórico é necessária.
Para exemplificar a exclusão tradicional de registros com Entity Framework, vamos considerar a seguinte entidade Product:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
Um método de exclusão tradicional precisa primeiramente localizar o registro no banco, e em seguida utilizar o método Remove do DbSet ou do DbContext, passando o registro como parâmetro. Em seguida, para persistir a operação, invocamos o método SaveChanges do DbContext:
public void DeleteProduct(int id)
{
var product = _context.Products.Find(id);
if (product != null)
{
_context.Products.Remove(product);
_context.SaveChanges();
}
}
Ao utilizar o método DeleteProduct, a linha equivalente será removida da tabela do banco de dados, não sendo possível recuperá-la posteriormente.
Implementando Soft Delete
Implementar Soft Delete com Entity Framework envolve algumas etapas principais. Primeiro, precisamos adicionar uma coluna à entidade Product que represente o status de exclusão:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
Aqui, “IsDeleted” é um campo booleano que indica se a entidade foi excluída com “true” ou não com “false”. Se sua entidade ainda não possuía uma propriedade equivalente, será necessário atualizar a tabela no banco de dados para criar a nova coluna, o que pode ser feito utilizando migrations.
Atualização ao invés de exclusão
Uma abordagem possível é substituir a lógica da deleção tradicional por uma atualização que marque o registro como excluído. Para isso, definimos a propriedade “IsDeleted” como true, indicando que o registro foi logicamente excluído. Neste caso, o método de exclusão teria a seguinte forma:
public void DeleteProduct(int id)
{
var product = _context.Products.Find(id);
if (product != null)
{
product.IsDeleted = true;
_context.SaveChanges();
}
}
Note que, comparando com o método de exclusão tradicional, basicamente removemos a chamada ao método Remove e adicionamos a alteração da propriedade IsDeleted.
Outra maneira de implementar o Soft Delete é sobrescrevendo o método “SaveChanges” do DbContext. Nesta abordagem, interceptamos todas as operações de exclusão e as transformamos em atualizações que definem a propriedade “IsDeleted” como “true” antes de serem salvas no banco de dados. Isso garante que qualquer tentativa de excluir um registro resulte na sua marcação como excluído, ao invés de ser removido fisicamente.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
}
}
return base.SaveChanges();
}
O primeiro passo que tomamos foi o de interceptar todas as operações de exclusão. Isso foi feito utilizando o “ChangeTracker” do Entity Framework, que rastreia todas as mudanças feitas nas entidades. Em seguida, filtramos as entradas (entries) que estão marcadas para exclusão (State == EntityState.Deleted) e que são da entidade Product. Para cada entidade marcada para exclusão, alteramos seu estado para “Modified” e definimos a propriedade “IsDeleted” como “true”. E por fim, chamamos o método “SaveChanges” da classe base para salvar as mudanças no banco de dados.
Ao utilizarmos essa abordagem, não precisamos mais alterar a propriedade IsDeleted no método de exclusão. Na verdade, este continuará como da forma tradicional, utilizando o Remove para fazer a exclusão. Mas na hora em que o SaveChanges for chamado, essa chamada será interceptada e alterada no DbContext.
Utilizando um filtro global de consultas
Um dos principais cuidados necessários ao utilizar soft delete é garantir que produtos excluídos não sejam listados na aplicação (a menos que seja necessário para o negócio). Isso pode ser feito de forma simples adicionando uma condição no método Where, por exemplo:
var products = _context.Products.Where(x => !x.IsDeleted);
No entanto, garantir que este filtro seja aplicado em todos os pontos do sistema pode ser um desafio e, ao falhar, pode gerar consequências graves para o negócio (imagine, por exemplo, se um cliente conseguir comprar um produto que foi excluído por erro no cadastro).
Felizmente o Entity Framework nos oferece o recurso de filtro global de consultas que, como o nome sugere, permite aplicar uma condição na listagem de uma entidade que será respeitada em qualquer ponto da aplicação. Isso é feito no mapeamento da entidade, da seguinte forma:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().HasQueryFilter(p => !p.IsDeleted);
base.OnModelCreating(modelBuilder);
}
Com essa configuração, todas as consultas para a entidade “Product” excluem automaticamente os produtos marcados como deletados, mantendo a integridade e a simplicidade das consultas em toda a aplicação.
Ignorando o filtro global
Se houver uma necessidade específica de consultar produtos marcados como excluídos, você pode usar o método “IgnoreQueryFilters()” temporariamente em uma consulta específica. Por exemplo, se quisermos implementar um método que retorna um produto pelo Id, mesmo que este tenha sido excluído, podemos fazer da seguinte forma:
var product = _context.Products.IgnoreQueryFilters()
.FirstOrDefault(p => p.Id == id);
Acelere a sua carreira conosco!
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
Conclusão
O Soft Delete com Entity Framework é uma abordagem eficaz para gerenciar registros de forma segura e eficiente em aplicações. Ao adotar esta técnica, você pode garantir que seus dados sejam mantidos com integridade e facilidade de recuperação, atendendo tanto a requisitos de segurança quanto de auditoria. Implementar Soft Delete pode requerer ajustes adicionais dependendo da complexidade do seu sistema, mas os benefícios em termos de gerenciamento de dados geralmente superam o esforço inicial de implementação.