TL;DR;
Is there any way to apply a query filter to WHERE clause of generated UPDATE and DELETE statement using EF Core when you call SaveChanges? (Quite similar to Global query filters which work just for SELECT scenarios.)
Global query filters work well for SELECT scenarios by adding additional criteria to WHERE clause of generated SELECT statement, but they don't do the same for UPDATE and DELETE statements when you call SaveChanges method of the DbContext and I wonder if it's possible to apply a query filter to WHERE clause of generated UPDATE and DELETE statement using EF Core when you call SaveChanges? The same way that it works by HasQueryFilter for SELECT statements.
I'm going to use such feature for a multi-tenancy scenario in a small application and I'm able to handle this requirement in other layers or by overriding SaveChanges and checking if the deleted or modified entity belongs to the right tenant; however, because of performance and security considerations, I prefer query filter approach.
More context
One of the common scenarios of query filters is Multi-tenancy. Data of a tenant should be protected against reading and modification by other tenants:
- Reading by another tenant: Registering a global query filter using
HasQueryFilterworks well. - Modification by another tenant: How to do it in EF DbContext?
You can take a look at query filters on Microsoft Docs or their sample code on GitHub.
Why am I looking for query filters for UPDATE and DELETE?
The main reasons are:
- Performance considerations
- Security considerations
After all, we have query filter for SELECT statement, why shouldn't we have for UPDATE and DELETE?
Performance considerations
I'm able to handle this requirement in other layers, like API, DAL, BLL or by overriding SaveChanges and checking if the deleted or modified entity belongs to the right tenant; The main reason that I prefer to not do it in BLL or SaveChanges, is because of performance. A WHERE clause will be evaluated at database server side, it's secure and also high performance, quite similar to global query filters.
For example, for a Delete scenario, an obvious answer could be something like this:
var entity = db.BlogPosts.Find(id);
if(entity!=null)
db.Entry(entity).State = EntityState.Deleted;
db.SaveChanges();
Which is basically a SELECT and then a DELETE.
But, I prefer to delete like this, which is just a DELETE statement:
db.Entry(new BlogPost() { Id = id }).State = EntityState.Deleted;
db.SaveChanges();
Security Considerations
The other thing is, first of all, these entities doesn't necessary have a TenantId in DbContext.Entries. On the other hand, even if they have a value for their TenantId, the value is not reliable, because it may have been changed. BlogPost.Id = 2 may belong to tenant 2, while tenant 1 is trying to modify or delete it.