Reputation: 33
I am trying to create a dynamic query generator, using generic arguments.
For example:
Expression<Func<T, bool>> CreateExpression<T>(
ParameterExpression parameter,
List<string> properties,
object? value,
string? operation) where T : class
Here T
is a Vulnerability
class:
public class vulnerability
{
public virtual ICollection<VulnCvssMetric> VulnCvssMetrics { get; } = new List<VulnCvssMetric>();
}
public class VulnCvssMetric
{
public int Score { get; set; }
}
Now I would like dynamically generate this expression, return all the vulnerabilities which have a VulnCvssMetric
score greater than 5.
The Linq expression would be:
DbContext.Set<Vulnerability>().Where(x => x.VulnCvssMetric.Any(y => y.Score > 5))
Please find the functions I have written below
public static Expression<Func<T, bool>> CreateExpression<T>(ParameterExpression parameter, List<string> properties, object? value, string? operation) where T:class
{
Expression expression = parameter;
// case 1: nested properties.
if (properties.Count() > 1)
{
// if the parameter is of type IEnumerable then we have to use the generic type arguments of that Enumerable to get the type.
// For example ICollection<Vulneribility> -- Generic type argument is Vulnerability which the parameter type.
bool isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
// change the parameter to the actual type, instead of collection.
// changed from ICollection --> Vulnerability
var nestedProperties = properties.Skip(1).ToList();
if (isCollection)
{
var parameterType = parameter.Type.GenericTypeArguments[0];
var nestedParameter = Expression.Parameter(parameterType, parameterType.Name);
expression = CreateExpression<T>(nestedParameter, nestedProperties, value, operation);
var anyMethod = typeof(Enumerable).GetMethods().Single(x => x.Name == "Any" && x.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(nestedParameter.Type);
var methodCallExpression = Expression.Call(anyMethod, parameter, expression);
return Expression.Lambda<Func<T, bool>>(methodCallExpression, parameter);
}
else
{
var nestedParameter = Expression.Parameter(Expression.Property(parameter, nestedProperties[0]).Type, "property");
expression = CreateExpression<T>(nestedParameter, nestedProperties, value, operation);
var type = parameter.Type;
return Expression.Lambda<Func<type,bool>>(expression, parameter)
}
}
else
{
// there is either only one property or no property.
Expression? property = parameter;
if (properties.Count() > 0 && properties[0] != null)
{
property = Expression.Property(parameter, properties[0]);
}
expression = Expression.MakeBinary(GetExpressionType(operation), property, Expression.Constant(value));
var type = parameter.Type;
return Expression.Lambda<Func<type, bool>>(expression, parameter);
}
}
private static ExpressionType GetExpressionType(string? operation)
{
if (operation == null)
throw new ArgumentNullException(nameof(operation));
switch (operation.ToLower())
{
case "lt": return ExpressionType.LessThan;
case "gt": return ExpressionType.GreaterThan;
case "lteq": return ExpressionType.LessThanOrEqual;
case "gteq": return ExpressionType.GreaterThanOrEqual;
case "eq": return ExpressionType.Equal;
case "neq": return ExpressionType.NotEqual;
default: return ExpressionType.Default;
}
}
The above program has an issue:
var type = parameter.Type;
return Expression.Lambda<Func<type,bool>>(expression, parameter)
These two lines shows an error:
Type is a variable and cannot be used as a type
I am not sure how I can pass type of that particular parameter when I am recursively calling the same method.
Could some please provide me the guidance on how to achieve this functionality, I want it to be generic so that I could pass different classes. Vulnerabiltiy
is just one example.
I call the create expression method by passing the following arguments.
var parameter = Expression.Parameter(typeof(Vulnerability), "vuln");
var properties = "Vulnerability.VulnCvssMetric.Score".Split(".").toList();
var operation = "gt";
var value = 5
CreateExpression(parameter, properties, value, operation);
Upvotes: 0
Views: 43