2

I am trying to override Savechanges in My Database Context to mark my entites as SoftDeleted.

I have an ISoftDeletable base which i override in my Entity classes

public interface IsoftDeletable
{
    public bool IsDeleted {get; set;}
}

And then in my Context

public override int SaveChanges()
{
    foreach (var entry in ChangeTracker.Entries()
              .Where(p => p.State == EntityState.Deleted))
        SoftDelete(entry);

    return base.SaveChanges();
}

private void SoftDelete(DbEntityEntry entry)
{
   var entity = entry.Entity as ISoftDeltable;
   entity.IsDeleted = true;
   entry.State = EntityState.Modified;
}

The above example works ok if i delete an Entity it gets soft deleted but the problem is that if that entity has Child Collections with Cascade delete, the child collections are not tracked by the Entity Framework and they are not marked as deleted.

One simple solution was to eagerly load all of the child Collections of the Entity that is going to be deleted before the delete happens so the Ef tracks the changes in the children too and soft delete them but this is a "hack" i want to avoid. It will be hard to remember all the relations between entities and eager load everything just before the delete happens. Also it will be hard to maintain this if the model changes.

Is there any better way to achieve that funcionallity?

Edit 1: I dont see how this question might be dublicate of this

Community
  • 1
  • 1
nick Georg
  • 77
  • 8
  • you can use reflection to see what navigation properties exist and see if they contain a foreign key, who matches your key, but that will require alot of changes, but it's still better than remember all of there properties manually. You can look up the primary key for the table via reflection, – johnny 5 Mar 08 '17 at 17:52
  • well yes this would require a lot of changes and it will be a a little messy. I am sure that there are better solutions though... – nick Georg Mar 08 '17 at 18:54
  • Possible duplicate of [Entity Framework soft delete implementation using database interceptor not working](http://stackoverflow.com/questions/34933981/entity-framework-soft-delete-implementation-using-database-interceptor-not-worki) (*do review the implementation, it might be a better way for you to implement this instead of hooking only into the change context*) – Igor Mar 08 '17 at 21:21
  • The reason i am not doing this is because i want to be able to have a super admin who can purge entities. In the change context it will be easy to check the identity and if the entity is deleted just continue with the delete command. In the interceptor this won't be easy – nick Georg Mar 09 '17 at 10:55
  • related question may help: https://stackoverflow.com/q/38998535/492 – CAD bloke Jul 18 '17 at 22:29

2 Answers2

2

I would try something on the DBMS side, not on the application side.

I'm gonna assume that you have MS SQL Server. MS SQL Server supports "on cascade update" in a similar fashion as "on cascade delete". It means that if you have an Person table with primary key PersonID and a Car table where there is a PersonID foreign key referencing the PersonID in the Person table, whenever you change the value in Person for PersonID, the foreign key in the related record in Car is also updated with this value.

So in your case what I would do is modify the foreign keys. Let's say you have two tables:

Person: (PersonId, Name, Age, IsDeleted)
Car: (CarId, Make, Model, PersonId, IsDeleted)

PersonID is the PK in Person, and CarId is the PK in Car. And instead of making Car.PersonId a FK referencing Person.PersonId, you create a composite foreign key: (Car.PersonId, Car.IsDeleted) => (Person.PersonId, Person.IsDeleted) AND you specify the "on update cascade" option for this composite FK. This way whenever you change Person.PersonId (which you never will) or Person.IsDeleted, the new values will be cascade updated into the corresponding Car.PersonId (which again, will not be cascade updated because Person.PersonId will not change) and Car.IsDeleted.

Akos Nagy
  • 4,201
  • 1
  • 20
  • 37
  • Thank you. I will consider this as an option. I just dont want to mess with my migration files because i use code first aproach. – nick Georg Mar 09 '17 at 07:47
  • Is there a way to do this from a code first approach? – Flater Jun 12 '18 at 08:05
  • I don't think so - at least not automagically. You can create the composite FK with EF, but you cannot specify the cascade update feature using EF (like the cascade delete). A possible option would be to add the ALTER statement to modify the FK to your migration files. – Akos Nagy Jun 12 '18 at 15:45
0

I find a solution. Hope It helps. If you change the entitystate than change deletedDate It's gonna be softdeleted. Then you can use query filter to reach undeleted ones.

public interface ICreatable
{
  DateTime CreatedDate { get; set; }
}

public interface IUpdatable
{
    DateTime UpdatedDate { get; set; }
}

public interface IDeletableEntity
{
    DateTime? DeletedDate { get; set; }
}

public abstract class AuditableEntity : BaseEntity, ICreatable, IUpdatable, IDeletableEntity
{
    public DateTime UpdatedDate { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime? DeletedDate { get; set; }
}

private void AssignDates()
{
    _context.ChangeTracker.Entries().ToList().ForEach(e =>
    {
        if (e.State == EntityState.Added && e.Entity is AuditableEntity addedEntity)
        {
            addedEntity.CreatedDate = DateTime.Now;
            addedEntity.UpdatedDate = DateTime.Now;
        }
        else if (e.State == EntityState.Modified && e.Entity is AuditableEntity updatedEntity)
        {
            updatedEntity.UpdatedDate = DateTime.Now;
        }
        else if (e.State == EntityState.Deleted && e.Entity is AuditableEntity deletedEntity)
        {
            e.State = EntityState.Modified;
            deletedEntity.DeletedDate = DateTime.Now;
        }
    });
}