Create expression trees using generic arguments which involves nested properties

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

Answers (0)

Related Questions