System.InvalidOperationException: Variable '' of type '' referenced from scope '', but it is not defined

2.9k Views Asked by At

I am trying to use entity framework global filter using expression in c#. I can able to frame expected expression either single or combined expression without any problem.

I applied the query filter for all the entity using a property in model builder as below

  1. Created a dummy class with property for whom query filter will be applied. (i.e. IsActive)
  2. Expression with that dummy class with be overridden using "VisitMember()" to create new expression which actually convert the member of dummy class to the entity type.

Note: I am tried to apply simple query fiter like "i => i.IsActive == true". Facing this error for both single and combined expression using "AndAlso".

As checked the expected query filter has been applied to the respective tables without any errors. I will call my modelbuilder extension method from my DbContext as like below

Codes in DbContext:

modelBuilder.GlobalFilters<FilterColumns>(i => i.TenantId == 1, nameof(FilterColumns.TenantId));

My Custom Class:

public class FilterColumns
{
   public int TenantId {get; set;}
}

My ModelBuilder Extension Method:

    public static void GlobalFilters<TClass>(this ModelBuilder modelBuilder, Expression<Func<TClass, bool>> expression, string property)
    {
        var body = expression?.Body;
        var baseType = expression?.GetType().GetGenericArguments().FirstOrDefault()?.GetGenericArguments().FirstOrDefault();
        var propertyType = baseType?.GetProperty(property)?.PropertyType;

        if (body != null)
        {
            if (property != null)
            {
                var allEntities = modelBuilder?.Model.GetEntityTypes().Where(e => e.FindProperty(property) != null).Select(e => e.ClrType).ToList();

                if (allEntities != null)
                {
                    foreach (var entity in allEntities)
                    {
                        var entityPropertyType = entity?.GetProperty(property)?.PropertyType;

                            if (entityPropertyType == propertyType)
                            {
                                var param = expression?.Parameters.Single().ToString();
                                var expressionVisitor = new CustomExpressionVisitor(entity, baseType, param.ToString());
                                var customExpression = expressionVisitor.Visit(expression?.Body);

                                var lambdaExpression = Expression.Lambda(customExpression, param);

                                modelBuilder?.Entity(entity).HasQueryFilter(lambdaExpression);
                            }                            
                    }
                }
            }
        }
    }

CustomExpressionVisitor Class:

public class CustomExpressionVisitor : ExpressionVisitor
{
    private readonly Type entityType;
    private readonly Type baseEntityType;
    private readonly string param;

    public CustomExpressionVisitor(Type entityType, Type baseEntityType, string param)
    {
        this.entityType = entityType;
        this.baseEntityType = baseEntityType;
        this.param = param;
    }
    protected override Expression VisitMember(MemberExpression expression)
    {
        if (expression.Member.DeclaringType == this.baseEntityType)
        {
            var convertedParam = Expression.Parameter(entityType, this.param);
            return Expression.PropertyOrField(convertedParam, expression.Member.Name);
        }

        return base.VisitMember(expression);
    }
}

I have expected the result from the table by applying the query filter but when I tried to access data from the table using linq query I am getting the following error. Can you anyone suggest your solution to resolve this error?

   at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)
   at System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterExpression node)
   at System.Linq.Expressions.ParameterExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMember(MemberExpression node)
   at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
   at System.Linq.Expressions.Compiler.VariableBinder.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
   at System.Linq.Expressions.Compiler.VariableBinder.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
   at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda)
   at System.Linq.Expressions.Expression`1.Compile(Boolean preferInterpretation)
   at System.Linq.Expressions.Expression`1.Compile()
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateExecutorLambda[TResults]()
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.CreateExecutorLambda[TResults]()
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](QueryModel queryModel)
   --- End of stack trace from previous location where exception was thrown ---
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database, IDiagnosticsLogger`1 logger, Type contextType)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass13_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Remotion.Linq.QueryableBase`1.GetEnumerator()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Collections.Generic.IEnumerable<TEntity>.GetEnumerator()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)

Thanks in advance.

1

There are 1 best solutions below

0
On

Lambda expression parameters are bound by instance, not by name as you seem to be expecting according to the code.

What you need basically is to create a parameter expression instance first, pass it to the expression visitor and use it inside to bind mapped member expressions, and finally use it when creating the new lambda expression.

So modify the visitor class as follows:

public class CustomExpressionVisitor : ExpressionVisitor
{
    private readonly Type entityType;
    private readonly Type baseEntityType;
    private readonly ParameterExpression param;

    public CustomExpressionVisitor(Type entityType, Type baseEntityType, ParameterExpression param)
    {
        this.entityType = entityType;
        this.baseEntityType = baseEntityType;
        this.param = param;
    }
    protected override Expression VisitMember(MemberExpression expression)
    {
        if (expression.Member.DeclaringType == this.baseEntityType)
        {
            return Expression.PropertyOrField(this.param, expression.Member.Name);
        }

        return base.VisitMember(expression);
    }
}

and the calling code as:

var param = Expression.Parameter(entity, "e");
var expressionVisitor = new CustomExpressionVisitor(entity, baseType, param);
var customExpression = expressionVisitor.Visit(expression?.Body);
var lambdaExpression = Expression.Lambda(customExpression, param);