Reputation: 2009
I'm dealing with dynamic queries and I'm not sure how to refactor this sample code code which basically would be giant if ladder if we wanted to support more operators / properties
var property = "AccountBalance";
var operator = ">";
var value = 5;
var query = _context.Users;
if (property == "AccountBalance")
{
if (operator == ">")
{
query = query.Where(x => x.AccountBalance > value);
}
}
I'd want to refactor it to something very generic and reusable like that:
// I simplified comparison here because afaik I'd have to use `CompareTo` due to IComparable
// instead of >, but I don't think it is important right now
public Expression<Func<T, bool>> GetExpression<T, U>(string operator, U value, Func<T, object property) where U : IComparable, T : class
{
switch (operator)
{
case ">":
return property > value;
case "<":
return property < value;
(...)
default:
throw new Exception("undefined operator");
}
}
Usage:
var expression = GetExpression<User, int>(">", 5, new Func<User> (x => x.AccountBalance));
query = query.Where(expression);
but I cannot figure out how to pass property into that function
new Func<User> (x => x.AccountBalance)
that's pseudo code idea
Upvotes: 4
Views: 415
Reputation: 7054
You can generate expression dynamically with Linq Expressions API
public static Expression<Func<T, bool>> GetExpression<T, U>(string property,
string @operator, U value) where U : IComparable
{
var p = Expression.Parameter(typeof(T), "p");
var member = Expression.PropertyOrField(p, property);
var constant = Expression.Constant(value);
var compareToArgument = Expression.Convert(constant, typeof(object));
var method = typeof(IComparable).GetMethod("CompareTo");
var call = Expression.Call(member, method, compareToArgument);
Expression body = null;
if (@operator == ">")
body = Expression.GreaterThan(call, Expression.Constant(0));
else if (@operator == "<")
body = Expression.LessThan(call, Expression.Constant(0));
// todo: throw if @operator is unknown
var lambda = Expression.Lambda<Func<T, bool>>(body, p);
return lambda;
}
Usage:
var expression = GetExpression<User, int>("AccountBalance", ">", 5);
query = query.Where(expression);
If you want to support more operators you can omit call to the CompareTo
method. Something like:
var p = Expression.Parameter(typeof(T), "p");
var member = Expression.PropertyOrField(p, property);
var constant = Expression.Constant(value);
Expression body = null;
if (@operator == ">=")
body = Expression.GreaterThanOrEqual(member, constant);
if (@operator == "<=")
body = Expression.LessThanOrEqual(member, constant);
Upvotes: 1
Reputation: 53958
You could add as a parameter of your method, GetExpression
, a Func<T,U>
, where T
is the type of the object you want to pass one of it's properties and U
is the type of the property. Then when you call your method you could pass a lambda.
E.g.
var expression = GetExpression<User, int>(">", 5, x => x.AccountBalance);
PS
You have to change the method signature as below:
public Expression<Func<T, bool>> GetExpression<T, U>
(string operator, U value, Func<T, U> property) where U : IComparable, T : class
Update
You could try something like this:
public Expression<Func<T, bool>> GetExpression<T, U>
(string op, U value, Func<T, U> property)
where T: class
where U : IComparable
{
switch (op)
{
case ">":
return p => value.CompareTo(property(p)) > 0;
case "<":
return p => value.CompareTo(property(p)) < 0;
default:
throw new Exception("undefined operator");
}
}
The very reason that you can't use operator >
and <
in this context can be found in the C# specification:
The < (less than), > (greater than), <= (less than or equal), and >= (greater than or equal) comparison, also known as relational, operators compare their operands. Those operators support all integral and floating-point numeric types.
U
is a generic type argument that cannot be constrained to be either an integral or a floating-point numeric type. For that reason we want U
to implement the IComparable
interface. Demanding this give us the opportunity to do the comparison we want.
Upvotes: 2