Reputation: 369
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.
Upvotes: 0
Views: 2054
Reputation: 205569
The problem is that _isDeleted
used for specifying the property name is coming from instance field of your derived context, and instance members of the context are treated specially in global query filters. Basically they are replaced with parameters, which in this particular case leads to "non translatable" construct.
The solution is to use static
or const
instead, e.g.
private static readonly string _isDeleted = "IsDeleted";
or
private const string _isDeleted = "IsDeleted";
Upvotes: 2
Reputation: 27282
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));
Upvotes: 0
Reputation: 6060
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.
Upvotes: 0