I'm trying to create a dynamic AndAlso
filter that will be used in a Where
method to a LINQ-to-EF
query:
query.Where(filterExpression)
where filterExpression
is a compiled lambda
So far I've implemented a loop with some fancy usage to check for cases where there is only one, two, or more queries.
Expression selectLeft = null;
Expression selectRight = null;
Expression filterExpression = null;
foreach (QueryColumn columnQuery in querycolumns)
{
Expression<Func<FileColumnRecords, bool>>
columnPredicate = d => d.fcv.Any(f => (f.value != null ?
f.value.ToLower().Contains(columnQuery.queryTerm.ToLower()) :
false));
if (selectLeft == null)
{
selectLeft = columnPredicate.Body;
filterExpression = selectLeft;
continue;
}
if (selectRight == null)
{
selectRight = columnPredicate.Body;
filterExpression =
Expression.AndAlso(selectLeft, selectRight);
continue;
}
filterExpression =
Expression.AndAlso(filterExpression, columnPredicate.Body);
}
Then I set up a ParameterReplacer
to ensure all iterations of my expression parameter get the same reference:
ParameterExpression param = Expression.Parameter(typeof(FileColumnRecords), "p");
ParameterReplacer replacer = new ParameterReplacer(param);
filterExpression = replacer.Visit(filterExpression);
which is built from:
class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression parameter;
internal ParameterReplacer(ParameterExpression parameter)
{
this.parameter = parameter;
}
protected override Expression VisitParameter
(ParameterExpression node)
{
return parameter;
}
}
(ParameterReplacer
class courtesy of JonSkeet)
When run the ParameterReplacer
I get the following error:
"Property 'System.String value' is not defined for type 'Models.FileColumnRecords'"
where FileColumnRecords
is defined as:
public class FileColumnRecords
{
public Documents doc;
public IEnumerable<FileColumnValues> fcv;
}
and FileColumnValues
as:
public partial class FileColumnValues
{
public long ID { get; set; }
public long CNID { get; set; }
public long fileID { get; set; }
public string value { get; set; }
}
You're running into difficulties due to there being parameters embedded within your expression tree, I suspect. The replacer should only replace the top-level parameters.
You could pass the top-level expressions into ParameterReplacer
:
class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression target;
private readonly ISet<ParameterExpression> sources;
internal ParameterReplacer(ParameterExpression target,
IEnumerable<LambdaExpression> expressions)
{
this.target = target;
sources = new HashSet<ParameterExpression>(
expressions.SelectMany(e => e.Parameters));
}
protected override Expression VisitParameter
(ParameterExpression node)
{
return sources.Contains(node) ? target : node;
}
}
You'd then need to change the rest of your code to build up the list of expression trees you've been combining. There are definitely more elegant ways of doing it, but I think that should work. That's just off the top of my head though - I haven't tried it.
Having said all this, are you sure you can't just use PredicateBuilder
instead?