Is there a way to prevent Entity Framework to NEVER read entries where a property is a specific value?

c# entity-framework-6

Question

I have a software that has been in the works for a while, today our client decided we NOT delete any data but instead hide them. To do this, I plan to add an "isDeleted" property to all tables and change all methods for deletion to set this property to "true" instead.

Problem is, I have 1000 times more reading than deletion, I can have a User and try to read all Comments of this User by using entity relation, I have to either add a "Where(x => !x.isDeleted)" to every single read like this or if it is possible, opt out ALL data that has isDeleted as true from being read.

Is the latter possible in any way? If not, is there an alternative to writing "Where(x => !x.isDeleted)" a thousand times?

1
0
3/4/2020 2:52:25 PM

Popular Answer

I've looked at this problem before in the past and rolling your own solution is much more difficult than you'd initially think, mostly because it's really hard to change how Include statements load the related entities (EF doesn't really allow you to filter them).

But there is a library that can do it for you.

Filtering the read results

It can be done quite easily using the EntityFramework.DynamicFilters library. (I am not in any way affiliated with the devs, I just really like their library)

The main readme actually has an example that fits your use case:

modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);

Essentially, it will only return results Where(d => !d.IsDeleted), which is exactly what you'd want. This filter is applied to all direct fetches and include statements, which means that those soft deleted entities are essentially non-existing as far as your domain is concerned.

This does assume that your entities all derive from a shared root which has the delete flag, which is something I'd advise you to do anyway.

Soft-deleting the entities

It's also possible to convert hard deletes into soft deletes in your database context itself, which means that you don't need to rewrite your delete code to instead update the entity (which can be a cumbersome rewrite, and it's always possible that someone forgets it here and there).

You can override the SaveChanges (and SaveChangesAsync) behavior in your context class. This allows you to find all the entities that are going to be deleted, and gives you the option to convert this into an update statement while also raising the IsDeleted flag.

It also ensures that no one can forget to soft delete. Your developers can simply hard delete the entities (when handling the code), and the context will convert it for them.

public class MyContext : DbContext
{
    public override int SaveChanges()
    {
        ConvertHardDeleteToSoftDelete();

        return base.SaveChanges();
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        ConvertHardDeleteToSoftDelete();

        return await base.SaveChangesAsync(cancellationToken);
    }

    private void ConvertHardDeleteToSoftDelete()
    {
        var deletedEntries = ChangeTracker
                                   .Entries<ISoftDelete>()
                                   .Where(entry => entry.State == EntityState.Deleted)
                                   .ToList();

        foreach (var entry in deletedEntries)
        {
            entry.State = EntityState.Modified;
            entry.IsDeleted = true;
        }
    }
}

Combined with the dynamic filter suggestion above, this means that such a soft deleted entity will not appear again in your application, but it will still exist in the database.

2
3/4/2020 3:18:18 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow