I am trying to retrofit soft delete to an ASP.NET Core 5.0 Application that uses EF Core.
It is done like this in the OnModelCreating
method of the DbContext
:
builder.Entity<MyEntity>().Property<bool>("IsDeleted");
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, "IsDeleted") == false);
This is recommended in the docs. Changing the underlying Database Entities (like MyEntity
) is something I would like to avoid.
The exception is thrown on code like this, which used to work flawlessly:
var myEntities= _context.MyEntities.AsNoTracking();
return _mapper.Map<IEnumerable<MyEntity>>(myEntities);
Leads to (on the second line):
InvalidOperationException: The LINQ expression 'DbSet<MyEntity>()
.Where(s => EF.Property<bool>(s, __ef_filter___isDeleted_0) == False)' could not be translated.
Because there are (naturally) a lot of places where AutoMapper is used, I would also like to NOT change the code used to return my DTO's. Adjusting the AutoMapper configuration would be ok.
PostgreSQL is used as the underlying database, if that makes any difference.
How do I need to configure my query filter, so that no exception is thrown for existing code?
// Edit:
I have simplified my example, the exact code uses a string for the property name:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, _isDeleted) == false);
I could change that variable.
CodePudding user response:
That thing here:
HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
is done because the field is private. You still need the field on your entity to make this work. You can, however, ignore it in your automapper config so that you do not need to adjust your dtos.
CodePudding user response:
You cannot use variable for property name. In this case EF creates parameter which is not translatable in this situation.
Solution is to generate query filter dynamically via helper function:
public static Expression<Func<T, bool>> GenerateDeletedFilter<T>(string propName)
{
var param = Expression.Parameter(typeof(T), "e");
var filter = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, param,
Expression.Constant(propName)), Expression.Constant(false)), param);
return filter;
}
And usage:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(GenerateDeletedFilter<MyEntity>(_isDeleted));