Creating an expression from the string of a proper

2019-02-15 17:59发布

I am trying to create a query based on some JSON, I currently have the JSON parsed into a set of rules, each rule contains the name of the field, the type of comparison (=, > etc) and the value to compare.

The issue I am having is getting it from that rule, to an IQueryable object, I am guessing I need to use reflection and somehow build the expression tree, but I'm not sure on the right approach...

Assuming I have:

public class Order : BaseEntity
{
    public int OrderID{ get; set; }
}

and I have the rule which is:

public class Rule
    {
        public string field { get; set; }
        public Operations op { get; set; }
        public string data { get; set; }
    }

Running it I get:

field = "OrderID"
op = "eq"
data = "123"

I have the method to parse it with the signature:

public IQueryable<T> FilterObjectSet<T>(IQueryable<T> inputQuery) where T : class

As part of this method I want to do:

inputQuery = inputQuery.Where(o => propertyInfo.Name == rule1.data);

This doesn't work because it basically just generates the sql "OrderID" = "123" which is obviously wrong, I need it to take the column name from inputQuery that has the same name as propertyInfo.Name and build the query that way...

Hope that made sense? Any suggestions?

Edit: I guess what I am asking is to convert a string (Because I can build one pretty simply from the rule) to an expression, maybe using Dynamic LINQ?

2条回答
孤傲高冷的网名
2楼-- · 2019-02-15 18:17

Something like this:

public static IQueryable<T> FilterObjectSet<T>(IQueryable<T> inputQuery, 
                                               Rule rule) where T : class
{
    var par = Expression.Parameter(typeof(T));

    var prop = Expression.PropertyOrField(par, rule.field);
    var propType = prop.Member.MemberType == System.Reflection.MemberTypes.Field ? 
                               ((FieldInfo)prop.Member).FieldType : 
                               ((PropertyInfo)prop.Member).PropertyType);

    // I convert the data that is a string to the "correct" type here
    object data2 = Convert.ChangeType(rule.data, 
                                      propType, 
                                      CultureInfo.InvariantCulture);

    var eq = Expression.Equal(prop, Expression.Constant(data2));
    var lambda = Expression.Lambda<Func<T, bool>>(eq, par);

    return inputQuery.Where(lambda);
}

If you need some explanation, you can ask. Note that this won't work on types that have special implicit conversions (like a MyString type that has an implicit conversion from string). This because Convert.ChangeType uses only the IConvertible interface.

Null handling for data is perhaps something else that should be handled.

Be aware that I'm not sure the Expression.PropertyOrField is handled by the various IQueryable<T> engines (LINQ-to-SQL and EF). I have only tested it with the AsQueryable() engine. If they don't "accept" it, you must split it in a Expression.Property or Expression.Field depending on what rule.field is.

A nearly equivalent version that doesn't use Expression.PropertyOrField:

public static IQueryable<T> FilterObjectSet<T>(IQueryable<T> inputQuery, 
                                               Rule rule) where T : class
{
    Type type = typeof(T);
    var par = Expression.Parameter(type);

    Type fieldPropertyType;
    Expression fieldPropertyExpression;

    FieldInfo fieldInfo = type.GetField(rule.field);

    if (fieldInfo == null)
    {
        PropertyInfo propertyInfo = type.GetProperty(rule.field);

        if (propertyInfo == null)
        {
            throw new Exception();
        }

        fieldPropertyType = propertyInfo.PropertyType;
        fieldPropertyExpression = Expression.Property(par, propertyInfo);
    }
    else
    {
        fieldPropertyType = fieldInfo.FieldType;
        fieldPropertyExpression = Expression.Field(par, fieldInfo);
    }

    object data2 = Convert.ChangeType(rule.data, fieldPropertyType);
    var eq = Expression.Equal(fieldPropertyExpression, 
                              Expression.Constant(data2));

    var lambda = Expression.Lambda<Func<T, bool>>(eq, par);
    return inputQuery.Where(lambda);
}
查看更多
唯我独甜
3楼-- · 2019-02-15 18:36

In the end I used a Dynamic Linq library I found on Guthrie's Blog:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Using this I was able to properly parse out and use the parameters I had built into the rules

查看更多
登录 后发表回答