Soft Delete en Entity framework core

Hoy vamos a explorar algo que mencioné en el post sobre eliminar datos, que es cuando queremos eliminar dichos datos, pero sin borrarlos de la base de datos, para que se quede el registro

 

Como siempre, este post es parte de un curso, para el cual tienes el código disponible en GitHub.

 

1 - Qué es soft delete en una base de datos? 

Para entender el concepto, lo más sencillo es que entendamos que es que cuando eliminamos un registro de la base de datos lo estamos eliminando completamente, si lo volvemos a buscar en la base de datos, este ya no estará disponible. 

 

Un soft delete es similar pero no igual, lo que hacemos es, en todas nuestras entidades, tener una propiedad llamada IsDeleted esta propiedad es un simple booleano que indica si su estado es eliminada o no, pero claro, está eliminada “lógicamente” ya que el registro como tal, está disponible en la base de datos. 

Esta acción es muy útil, ya que nos permite mantener el registro y bueno, si eliminamos algo por error o por un bug, no se elimina completamente. Además nos puede servir para tener un poco de visibilidad con ciertas acciones.

Algo que a mi me gusta mucho, además es incluir un timestamp con cuando se ha eliminado, o cuando fue la última vez que se modificó. 

 

Esta información es puramente informativa por si algún día descubres un registro que está borrado y no debería tener información de cuando sucedió e ir a buscar información en los logs correspondientes.

  • NOTA: Si quieres mantener una trazabilidad de todo lo que sucede en tu entidad, recomiendo que utilices event sourcing, o bitemporal data modeling.

 

 

2 - Implementar soft delete en entity framework core

Para implementar soft delete en EF Core lo que tenemos que hacer es añadir dichas propiedades a la entidad. Mi recomendación personal es que TODAS las entidades lo tengan implementado, y por lo tanto, sea parte de una clase base:

public abstract class CursoEFBaseEntity
{
    public bool IsDeleted { get; set; }
    public DateTime DeletedTimeUtc { get; set; }
}

Y luego cada una de tus entidades debe implementar dicha clase base.

public class User : CursoEFBaseEntity
{
    ...
}
public class Wokringexperience : CursoEFBaseEntity
{
    ...
}

 

Ahora lo que tenemos que hacer es, cuando hacemos queremos eliminar un registro, en vez de utilizar el método .Remove(), lo que hacemos es un update, con la propiedad IsDeleted=true.

public async Task<bool> Delete(int id)
{
    User? user = await _context.Users
        .FirstOrDefaultAsync(x => x.Id == id);

    if (user == null)
        return false;

    user.IsDeleted = true;
    user.DeletedTimeUtc = DateTime.UtcNow;

    _context.Users.Update(user);
    return true;
}

Si probamos ahora, veremos que el registro de la base de datos a pasado a eliminado:

registro eliminado

  • Nota: Dependerá de tu arquitectura el saber sí tienes que marcar todos los registros que hacen referencia a tu entidad principal como delete true también o no, en algunos casos verás que es necesario, en otros no lo será tanto, como digo, depende de cada empresa/dominio.

 

Como te puedes imaginar esta propiedad es simplemente una propiedad, ni entity framework ni la base de datos van a hacer nada de forma mágica para no mostrar el registro si se lo pides. por lo que si vas ahora a tu api y pides el Id correspondiente, te lo devolverá con la propiedad IsDeleted y el valor true

is deleted true on api

 

Para que Entity framework lo oculte tenemos dos opciones. 

 

La primera, y más obvia, es, actualizar todas las consultas para poner un where y excluir todos los registros donde el isDeleted es true. Lo cual puede ser una verdadera pesadilla si tenemos muchas consultas, además tendrías que actualizar todos los includes, etc. 

 

La segunda y más eficiente es configurar esta información en nuestro método OnModelCreating del DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
        .HasQueryFilter(a => !a.IsDeleted);   
}

Cuando utilizamos HasQueryFilter, lo que estamos haciendo es añadir ese filtro a todas las consultas SQL que se vayan a ejecutar sobre dicha entidad.

Esta opción es mucho mejor, pero igual que vimos en el post de data seed; podemos delegar esta configuración a sus clases específicas (si las tienes creadas):

public void Configure(EntityTypeBuilder<User> builder)
{
    builder.HasQueryFilter(a => !a.IsDeleted);
    
    builder.HasData(
        new User { Email = "[email protected]", Id = 1, UserName = "user1", IsDeleted = false},
        new User { Email = "[email protected]", Id = 2, UserName = "user2", IsDeleted = false }
    );
}

Que por supuesto también funciona.

 

 

Si ejecutamos el código y corremos el mismo endpoint que antes, vemos que no nos devuelve nada. 

isdeleted working

Además, si comprobamos la query SQL ejecutada, vemos que tiene el filtro especificado. 

entity framework filters query explalined

 

2.1 - Limitaciones de los filtros globales

Hay que tener en cuenta que los filtros pueden tener algunas limitaciones, por ejemplo, con Entity Framework podemos ejecutar consultas SQL directamente escribiendolas en el código, si hacemos eso, nos estaremos saltando el filtro.

 

O por ejemplo, hay otra función llamada IgnoreQueryFilters que omite todos los filtros globales. 





Espero que este post te sea de utilidad y si tienes cualquier pregunta no dudes en hacerla por twitter o por youtube. 

 

Un saludo! 


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café