Saibamen
Saibamen

Reputation: 648

Lambda IfThenElse expression with another lambda inside (for SetQueryFilter())

My goal is to have filter for IMutableEntityType, where I select data rows by CurrentTenantId column value. For more context, please see: https://github.com/dotnet/efcore/issues/23718

This is already done by me:

var parameter = Expression.Parameter(entityType.ClrType, "p");
                
// TODO: This filter is working, but we must prevent filtering with TenantId = 0
var workingFilter = Expression.Lambda(
    Expression.Equal(
        Expression.Property(parameter, colName),
        // See https://github.com/dotnet/efcore/issues/23718
        Expression.MakeMemberAccess(Expression.Constant(myContext), currentTenantIdMemberInfo)
        ),
    parameter);

entityType.SetQueryFilter(filter);

But I want to get all entities if TenantId == 0, so I wrote this Lambda:

var conditionalExpression = Expression.IfThenElse(
    // Check if TenantId > 0
    Expression.GreaterThan(
        Expression.MakeMemberAccess(Expression.Constant(myContext), currentTenantIdMemberInfo),
        Expression.Constant((long) 0)
        ),
    // build filter: (p) => p.{colName} == {myContext.CurrentTenantId}
    // i.e. (p) => p.TenantId == 123)
    Expression.Lambda(
        Expression.Equal(
            Expression.Property(parameter, colName),

            // See https://github.com/dotnet/efcore/issues/23718
            Expression.MakeMemberAccess(Expression.Constant(myContext), currentTenantIdMemberInfo)
            )//,
        ),
    // TenantId <= 0, so act like (p => p)
    Expression.Lambda(Expression.Constant(parameter)) // just p // TODO: Not working
    //Expression.Lambda(Expression.Constant(parameter), parameter) // p => p // TODO: Also not working...
);

var filter = Expression.Lambda(conditionalExpression);

But after calling entityType.SetQueryFilter(filter); I have InvalidOperationException:

'The filter expression '() => IIF((value(My.Project.MyContext).CurrentTenantId > 0), () => (p.TenantId == value(My.Project.MyContext).CurrentTenantId), () => p)' specified for entity type 'MyEntity' is invalid. The expression must accept a single parameter of type 'My.Project.MyEntity' and return bool.'

And my question is - how I can achieve my goal?

Upvotes: 0

Views: 226

Answers (1)

Svyatoslav Danyliv
Svyatoslav Danyliv

Reputation: 27282

Something like this. You have missed adding parameter to Lambda and made mistakes with lambda usage. Also selected shortest way to do that.

var parameter = Expression.Parameter(entityType.ClrType, "p");

// EF just needs to know that you have member access to DbContext
var contextExpr = Expression.Constant(null, typeof(MyContext));
var tenantExpr = Expression.MakeMemberAccess(contextExpr, currentTenantIdMemberInfo);

var conditionalExpression = Expression.OrElse(
    // Check if TenantId <= 0
    Expression.LessThanOrEqual(
        tenantExpr,
        Expression.Constant((long) 0)
        ),
        Expression.Equal(
            Expression.Property(parameter, colName),
            tenantExpr
        )
    )
);

var filter = Expression.Lambda(conditionalExpression, parameter);

entityType.SetQueryFilter(filter);

Also you are not alone and this task has been done by a lot of libraries, for example ASP.NET Boilerplate

For simplifying such tasks, I strongly recommend to use Expression Tree visualisers: https://github.com/agileobjects/ReadableExpressions https://github.com/zspitz/ExpressionTreeVisualizer

Upvotes: 1

Related Questions