Heldenkrieger01
Heldenkrieger01

Reputation: 369

EF.Property throws "The LINQ expression could not be translated"

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

Answers (3)

Ivan Stoev
Ivan Stoev

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

Svyatoslav Danyliv
Svyatoslav Danyliv

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

DasKr&#252;melmonster
DasKr&#252;melmonster

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

Related Questions