How to Implement a Soft Delete Strategy with Entity Framework Core
[删除(380066935@qq.com或微信通知)]
更好的阅读体验请查看原文:https://blog.jetbrains.com/dotnet/2023/06/14/how-to-implement-a-soft-delete-strategy-with-entity-framework-core/
I’m sure we’ve all had a turbulent relationship with the definition of “delete” throughout our development careers. So what does the user mean when they say “delete my data”? Well, if you’re anything like me, you’ll quickly realize the user wants to logically delete information from cluttering the user interface and not permanently delete records out of existence… Oops 😬.
With hard lessons learned, many developers will turn to a Soft Delete strategy allowing them to reverse accidental deletes, maintain data integrity, and general administrative oversight. You may also be required by law to retain data for a certain period, and this strategy can help you accomplish those requirements.
This post will explore how to implement a Soft Delete strategy with Entity Framework Core and how to use it during writing and reading to your database engine of choice.
What is a Soft Delete?
As alluded to in the introduction, there are two kinds of deletes in application development: physical and logical.
A physical delete removes a record from a data store and is highly destructive. Data deleted through a physical delete is lost, and only system administrators can recover the data, typically using extreme measures or backups. Physical deletes commonly use a mechanism of the data storage engine to execute a non-reversible command. For example, SQL-based databases can run DELETE
statements to remove records from a table (hands up if you’ve ever accidentally forgotten the WHERE
clause).
Conversely, a soft delete is a logical decision made by the development team to mark records to ignore during queries. Root elements of a data model will have a flag of some kind, either a boolean flag or a timestamp indicating the time of deletion. Queries applied to root elements must explicitly specify whether to use the deletion indicator as a factor in producing a result set.
For example, here’s a SQL query returning a record, but only if the IsDeleted
bit column is set to 0 for “false”.
If you or your user would like to recover data, recovering deleted data is as straightforward as changing the value of the deletion indicator.
Soft delete markers are more challenging to implement into an existing system, as it takes some thought about when and where to apply deletion indicators. Additionally, there can be some overhead in the form of additional indexes and a growing record count. These are drawbacks worth considering if you have limited disk space or I/O limitations.
Now that you have a general idea of what constitutes a Soft Delete strategy let’s go ahead and implement it using Entity Framework Core.
Entity Framework soft deletes with Interceptors
Entity Framework Core includes a concept of interceptors – an approach to extending the execution pipeline. There are several types of interceptors, and standard implementations allow you to modify the SQL command, alter entities before you save any changes, and use auditing techniques.
In this example, you’ll use an interceptor to modify entities during the writing phase of the application. First, let’s define a Movie
entity, which you will adjust to support soft deletes.
To allow for increased reuse, we’ll create an ISoftDelete
interface, giving you shared properties and implementation to undo any delete. Any of the properties, IsDeleted
or DeletedAt
, is sufficient for a soft delete strategy, but I’ve added both in this example for maximum verbosity. If you want to adopt this soft-delete approach, you’’ probably only want to have one of these properties.
Apply the interface to the Movie
entity, and look at the final entity definition.
While you could set the deleted flag on every entity you want to delete, that would be tedious and not to mention error-prone. So, let’s take advantage of EF Core infrastructure and write a SoftDeleteInterceptor
.
As you call SaveChanges
on a DbContext
instance, this interceptor will check to see if any entry in the change tracker implements ISoftDelete
. If so, the interceptor will change the entity state of Deleted
to Modified
and set all soft delete properties.
As you can see in the implementation, this interceptor works with EF Core constructs before invoking any database-specific functionality. This interceptor will work with any database provider supported by EF Core, including, but not limited to, SQL Server, PostgreSQL, SQLite, and MySQL. For this sample, I’ve used the Microsoft.EntityFrameworkCore.InMemory
package, but feel free to substitute your favorite provider.
The final step to complete your writing phase modifications is registering the interceptor with a DbContext
definition using the call to AddInterceptors
during the OnConfiguring
phase of initialization.
Any attempt to remove an entity from the database using the EF Core DbContext
will switch from a delete to an update statement. Let’s go ahead and see it in action.
As you may have noticed, the code looks like regular old EF Core. What about reading data? How do you filter out deleted records? You’ll see how to do that in the next section.
Automatically filter soft-deleted records
Marking records to be deleted is only half the story. With a single configuration, you can tell EF Core to ignore soft-deleted records when executing queries, and you can do that using query filters on our entity definitions. For example, the modified DbContext
definition with a query filter on the Movies
collection is here.
You can apply as many query filters as you like, but I suggest limiting query filters to what’s necessary, as they are typically invisible in a LINQ query. The “invisible” nature of query filters means developers on your team have to understand and manage these concepts in their minds. With too many filters, it can get confusing and lead to unexpected bugs.
Putting it all together with reads and writes
I’ve created a quick sample application below, something you might expect to see given the DbContext
from previous sections.
You’ll notice no mention of the IsDeleted
flag anywhere in the code. The lack of ISoftDelete
properties in read/write usage is because the EF Core interceptor and query filter use the properties transparently.
Additionally, to negate query filters, you can use IgnoreQueryFilters
on any LINQ query, and you’ll get unadulterated access to form a LINQ query.
Let’s take a look at the complete application in a single file.
Executing the program above will give you the following output.
So straightforward and much easier to accomplish now with EF Core than in previous iterations. This a reminder that no data is physically deleted, only logically “deleted”. The code to insert/delete entities remains the same, and querying also does not look any different from routine EF queries. All thanks to the power of EF interceptors.
Conclusion
The soft delete strategy can help you provide the user experiences you intend without the risk of catastrophically destroying data. While the technique has some overhead, you can overcome these challenges using EF Core infrastructure and a simple interface. You’ll likely want to add additional indexes to your tables for the deletion flags to speed up queries. In addition, you can apply query filters and interceptors to other problems. It is good knowledge when looking at different approaches to solving complex business tasks.
Thank you for reading, and if you have any comments or questions, please feel free to leave them in the comment section.