Add to SearchTerm
a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A
, B
, C
, D
are the SearchTerm[0]
, SearchTerm[1]
, SearchTerm[2]
, SearchTerm[3]
and opB
, opC
, opD
are the operators defined in SearchTerm[1].LogicalConnector
, SearchTerm[2].LogicalConnector
, SearchTerm[3].LogicalConnector
.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm
collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor
. You need an ExpressionVisitor
when you are trying to "merge" multiple LambdaExpression
s that have distinct ParameterExpression
. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>()
for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1"
with x2 => x2.Foo == "Foo2"
then you need an ExpressionVisitor
that replaces x2
with x1
, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2"
. In the code given we have only x1
(that is var obj = ExpressionHelper.Parameter<TEntity>()
), so no problem.