Search code examples
c#entity-framework-corespecification-pattern

EF Core 8 specification pattern variable referenced from scope ', but it is not defined


When invoke the expression, i become an InvalidOperationException

variable '___zip_0' of type 'System.Int32' referenced from scope '', but it is not defined

SpecificationBase class

     public abstract class Specification<TEntity> where TEntity : class
        {
            public abstract Expression<Func<TEntity, bool>> ToExpression();
        
            public bool IsSatisfiedBy(TEntity entity)
            {
                Func<TEntity, bool> predicate = ToExpression().Compile();
                return predicate(entity);
            }
           
            public Specification()
            {
                
            }
            public Specification( Expression<Func<TEntity,bool>>? criteria) 
                => Criteria = criteria;
            public Expression<Func<TEntity, bool>> Criteria { get; set; }
        
            public List<Expression<Func<TEntity, object>>> IncludeExpression { get; } = new();
            public List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>> NestedIncludes { get; }
                    = new List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>();
            public List<string> IncludeStrings { get; } = new List<string>();
            public Expression<Func<TEntity,object>>? OrderByExpression { get; private set; }
            public Expression<Func<TEntity,object>>? OrderByDescendingExpression { get; private set; }
        
            protected void AddInclude(Expression<Func<TEntity,object>> includeExpression)=> IncludeExpression.Add(includeExpression);
            protected virtual void AddNestedInclude(Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> nestedIncludeExpression)
            {
                NestedIncludes.Add(nestedIncludeExpression);
            }
            protected virtual void AddInclude(string includeString)
            {
                IncludeStrings.Add(includeString);
            }
            protected void AddOrderBy(Expression<Func<TEntity, object>> orderByExpression)
                => OrderByExpression = orderByExpression;
        
            protected void AddOrderByDescending(Expression<Func<TEntity, object>> orderByDescendingExpression)
               => OrderByDescendingExpression = orderByDescendingExpression;
        
            public Specification<TEntity> And(Specification<TEntity> specification)
            {
                return new AndSpecification<TEntity>(this, specification);
            }
           
            public Specification<TEntity> Or(Specification<TEntity> specification)
            {
                return new OrSpecification<TEntity>(this, specification);
            }
           
        }
    

Specification in use

          
   public class AddressByZipSpecification : Specification<Address>
        {
                public int _zip { get; set; }
                public AddressByZipSpecification(int zip)
                {
                    _zip = zip;
                }
                public override Expression<Func<Address, bool>> ToExpression()
                {
                    return address => address.Zip == _zip;
                }
        }

How i use the specification in the test

 var zipSpec = new AddressByZipSpecification(12);
  var address = await dbContext.Address.Where(a =>  zipSpec.ToExpression().Invoke(a)).ToListAsync();

I am at a loss, what happend in the expression tree


Solution

  • You can't Invoke an Expression inside another EF Core Expression and expect it to work.

    Your easiest option is to cache the Expression and pass it in directly

    var zipExpr = zipSpec.ToExpression();
    var address = await dbContext.Address.Where(zipExpr).ToListAsync();
    

    If you need to combine this expression with other expressionn then you may want to look at LinqKit, which can transform expression calls within other expressions.