Search code examples
c#linqexpression-trees

Is List<T> a constant, an expression parameter, or something else in Expression Tree?


Let's say I have this lambda expression that I want to write an expression Tree on:

query.Where(d => (allCk && d.FacilityId == facilityId) //1.
      ||
     (!allCk && allSelected && d.FacilityId == facilityId  && !ids.Contains(d.Id)) //2.
      ||
     (!allCk && !allSelected && ids.Contains(d.Id))) //3.

This is how I've managed to write it: For brevity, I'll only show the second condition, which the most complex of all (!allCk && allSelected && d.FacilityId == facilityId && !ids.Contains(d.Id)).

private Expression<Func<Documents, bool>> GetDocumentsPredicate(
        int facilityId, bool allCk, bool allSelected, List<int> ids)
{
  ParameterExpression pe = Expression.Parameter(typeof(Documents), "d");

  var listExpr1 = new List<Expression>();
  listExpr1.Add(Expression.IsFalse(Expression.Constant(allCk)));  //allCk
  listExpr1.Add(Expression.Constant(allSelected));                //allSelected

  var facilityParam = Expression.Constant(facilityId);            //facility
  Expression facilityIdProp = Expression.Property(pe, "FacilityId");
  Expression facilityIdEql = Expression.Equal(facilityIdProp, facilityParam);
  listExpr1.Add(facilityIdEql);

  //This is where I'm having trouble... Is ids a parameter or a constant???
  //Assuming it's a parameter...

  ParameterExpression peIds = Expression.Parameter(typeof(List<int>), "ids");
  Expression listContains = Expression.Call(
           pIds,
           typeof(List<int>).GetMethod("Contains"),
           Expression.Property(pe, "Id"));
  listExpr1.Add(Expression.Call(peIds, listContains, Expression.Property(pe, "Id"))); 
  var exp1 = listExpr1
             .Skip(1)
             .Aggregate(listExpr1[0], Expression.AndAlso);

  //...

}

I'm getting an error at this line: listExpr1.Add(Expression.Call(pIds, listContains, Expression.Property(pe, "Id"))); // Cannot convert 'Linq.Expression.ParameterExpression' to 'System.Reflection.MethodInfo'`. So, it's complaining about the pIds.

So What is ids in here, a constant, a parameter, or something else?

Thanks for helping

EDIT

This is how I wrote the entire method

private Expression<Func<Documents, bool>> GetDocumentsPredicate(
    int facilityId, bool allCk, bool allSelected, List<int> ids)
{
  ParameterExpression pe = Expression.Parameter(typeof(Document), "d");
  Expression constIds = Expression.Constant(ids);
  Expression allCkParam = Expression.Constant(allCk);
  Expression allSelectedParam = Expression.Constant(allSelected);

  Expression listContains = Expression.Call(
           constIds,
           typeof(List<int>).GetMethod("Contains"),
           Expression.Property(pe, "Id"));

  /*(allCk && d.FacilityId == facilityId) ==> exp0*/
  var facilityParam = Expression.Constant(facilityId);


  Expression facilityIdProp = Expression.Property(pe, "FacilityId");
  Expression facilityIdEql = Expression.Equal(facilityIdProp, facilityParam);

  Expression exp0 = Expression.AndAlso(allCkParam, facilityIdEql);

/*(!allCk && allSelected && d.FacilityId == facilityId && !ids.Contains(d.Id))) 
  ==> exp1 */
  var listExpr1 = new List<Expression>();
  listExpr1.Add(Expression.IsFalse(allCkParam));
  listExpr1.Add(allSelectedParam);
  listExpr1.Add(facilityIdEql);
  listExpr1.Add(Expression.IsFalse(listContains));

  var exp1 = listExpr1
            .Skip(1)
            .Aggregate(listExpr1[0], Expression.AndAlso);


   /* (!allCk && !allSelected && ids.Contains(d.Id)) ==> exp2 */
   var listExpr2 = new List<Expression>();
   listExpr2.Add(Expression.IsFalse(allCkParam));
   listExpr2.Add(Expression.IsFalse(allSelectedParam));
   listExpr1.Add(listContains);

   var exp2 = listExpr2
            .Skip(1)
            .Aggregate(listExpr2[0], Expression.AndAlso);

   var listExpr = new List<Expression> { exp0, exp1, exp2 };
   var exp = listExpr
            .Skip(1)
            .Aggregate(listExpr[0], Expression.OrElse);

    var expr = 
              Expression.Lambda<Func<Document, bool>>(exp, 
              new ParameterExpression[] { pe });
    return expr;
 }

This is the result I'm getting when hovering over the returned value

d => (((False AndAlso (d.FacilityId == 1)) 
     OrElse 
     (((IsFalse(False) AndAlso False) AndAlso (d.FacilityId == 1)) 
        AndAlso 
        IsFalse(value(System.Collections.Generic.List`1[System.Int32]).Contains(d.Id)))) 
     OrElse 
    ((IsFalse(False) AndAlso IsFalse(False)) 
        AndAlso 
        value(System.Collections.Generic.List`1[System.Int32]).Contains(d.Id)))

A simple test with this statement var count = query.Count(); produces an exception: Unknown LINQ expression of type 'IsFalse'.


Solution

  • ids is not a parameter, as far as the expression tree is concerned, because it is not passed to Func<...>. The list of ids against which you need to match is built into the structure of the expression, hence Expression.Constant is the proper way to handle it.

    However, that's not the whole story: since ids is a reference type, only a reference to it is going into the structure of the expression. Therefore, if you decide to change the content of the list after constructing the expression, the meaning of the ids.Contains is going to change.

    If this is not the effect that you would like to achieve, make a copy of the list:

    Expression constIds = Expression.Constant(ids.ToList());