Skip to content

Instantly share code, notes, and snippets.

@phillippelevidad
Last active October 19, 2018 14:56
Show Gist options
  • Save phillippelevidad/2ffb6454f69bc5a89aeca998876679aa to your computer and use it in GitHub Desktop.
Save phillippelevidad/2ffb6454f69bc5a89aeca998876679aa to your computer and use it in GitHub Desktop.
EF Core 2 implementation of SoftDeletes
/*
* EF Core implementation of SoftDeletes.
*
* Just specify entity types that should not be hard deleted in the SoftDeleteMapper class
* and this code will handle the rest, by:
* 1. Adding the IsDeleted and DeletedAt columns;
* 2. Applying a query filter to prevent soft-deleted entries from being returned on reads;
* 3. Automatically rewriting deletes so that they only set entries as deleted, preventing hard-deletes.
*/
namespace Infra.Data.Mapping
{
interface IMapper
{
void Map(ModelBuilder modelBuilder);
}
class SoftDeleteMapper : IMapper
{
public static Type[] Types = new Type[]
{
// TODO: specify entity types.
typeof(Foo),
typeof(Bar),
typeof(FooBar)
};
public void Map(ModelBuilder modelBuilder)
{
foreach (var type in Types)
{
var entityType = modelBuilder.Entity(type);
// Add properties.
entityType.Property<bool>("IsDeleted");
entityType.Property<DateTime?>("DeletedAt");
// Auto filter queries by IsDeleted.
var parameter = Expression.Parameter(type);
// EF.Property<bool>(entity, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
var compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// entity => EF.Property<bool>(entity, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
entityType.HasQueryFilter(lambda);
}
}
}
}
namespace Infra.Data
{
public class AppDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var mappers = new IMapper[]
{
new SoftDeleteMapper()
};
foreach (var m in mappers)
m.Map(modelBuilder);
}
public override int SaveChanges()
{
OnBeforeSaving();
base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
await base.SaveChangesAsync(cancellationToken);
}
void OnBeforeSaving()
{
RewriteDeletesForSoftDeletableEntities();
}
void RewriteDeletesForSoftDeletableEntities()
{
foreach (var type in SoftDeleteMapper.Types)
{
foreach (var entry in ChangeTracker.Entries())
{
if (!type.IsAssignableFrom(entry.Entity.GetType()))
continue;
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
entry.CurrentValues["DeletedAt"] = DateTime.UtcNow;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment