LINQ表达式构建获取属性并转换为类型

时间:2016-03-03 22:57:14

标签: c# linq

我正在尝试根据CodeProject文章here过滤数据,但我对非字符串的值有困难。这就是我现在正在做的事情:

public static class ExpressionBuilder
    {
        private static readonly MethodInfo containsMethod = typeof(string).GetMethod("Contains");
        private static readonly MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
        private static readonly MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
private static Expression GetExpression<T>(ParameterExpression param, Filter filter)
        {
            //the property that I access here is stored as a string
            MemberExpression member = Expression.Property(param, filter.PropertyName);
            //this value is brought in from a web request as a string, but could be a numeric, datetime, or other type
            ConstantExpression constant = Expression.Constant(filter.Value);

            try
            {
                decimal numericValue = 0;
                if (decimal.TryParse(filter.Value.ToString(), out numericValue))
                {
                    var numericConstant = Expression.Constant(numericValue);
                    var numericProperty = ConvertToType(param, (PropertyInfo)member.Member, TypeCode.Decimal);
                    switch (filter.Operation)
                    {
                        case Enums.Op.Equals:
                            return Expression.Equal(member, numericConstant);
                        case Enums.Op.NotEqual:
                            return Expression.NotEqual(member, numericConstant);
                        case Enums.Op.Contains:
                            return Expression.Call(member, containsMethod, constant);
                        case Enums.Op.StartsWith:
                            return Expression.Call(member, startsWithMethod, constant);
                        case Enums.Op.EndsWith:
                            return Expression.Call(member, endsWithMethod, constant);
                        case Enums.Op.GreaterThan:                            
                            return Expression.GreaterThan(numericProperty, numericConstant);
                    }
                }
                else
                {
                    //this part works fine for values that are simple strings.
                    switch (filter.Operation)
                    {
                        case Enums.Op.Equals:
                            return Expression.Equal(member, constant);
                        case Enums.Op.NotEqual:
                            return Expression.NotEqual(member, constant);
                        case Enums.Op.Contains:
                            return Expression.Call(member, containsMethod, constant);
                        case Enums.Op.StartsWith:
                            return Expression.Call(member, startsWithMethod, constant);
                        case Enums.Op.EndsWith:
                            return Expression.Call(member, endsWithMethod, constant);
                        case Enums.Op.GreaterThan:
                            return Expression.GreaterThan(member, constant);
                    }
                }                
            }    
            return null;
        }

private static MethodCallExpression ConvertToType(ParameterExpression source, PropertyInfo sourceProperty, TypeCode type)
        {
            var sourceExProperty = Expression.Property(source, sourceProperty);
            var typeChangeMethod = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(TypeCode) });
            var returner = Expression.Call(typeChangeMethod, sourceExProperty, Expression.Constant(type));
            return returner;
        }
}

public class Filter
    {
        public string PropertyName { get; set; }
        public int Key { get; set; }
        public string Operator { get; set; }
        public Enums.Op Operation
        {
            get
            {                
                switch (Operator.Trim())
                {
                    case "<=":
                        return Enums.Op.LessThanOrEqual;
                    case ">=":
                        return Enums.Op.GreaterThanOrEqual;
                    case "=":
                        return Enums.Op.Equals;
                    case "<":
                        return Enums.Op.LessThan;
                    case ">":
                        return Enums.Op.GreaterThan;
                    case "not equal to":
                        return Enums.Op.NotEqual;
                    case "contains":
                        return Enums.Op.Contains;
                    case "starts with":
                        return Enums.Op.StartsWith;
                    case "ends with":
                        return Enums.Op.EndsWith;
                    default:
                        return new Enums.Op();                        
                }                
            }
        }
        public object Value { get; set; }
    }

当我尝试基于数字进行过滤时,例如(值&lt; 12),我得到一个异常,你无法将Object与小数进行比较。 ConvertToType实际上是不是将字符串中的值转换为小数?

提前致谢!

1 个答案:

答案 0 :(得分:2)

在您的情况下,最简单的方法是将filter.Value视为与属性中相同的对象类型,然后您可以将此类型设为

var propInfo = (PropertyInfo)member.Member;
var constantValue = Expression.Constant(filter.Value, propInfo.PropertyType);

var typeCode = Type.GetTypeCode(propInfo.PropertyType);
var property = Expression.Property(param, propInfo);
switch (filter.Operation)
{
    case Enums.Op.Equals:
        return Expression.Equal(property, constantValue);
    case Enums.Op.NotEqual:
        return Expression.NotEqual(property, constantValue);
    case Enums.Op.GreaterThan:
        return Expression.GreaterThan(property, constantValue);
}

在这种情况下,它不仅适用于特定的十进制类型,而且适用于int,string和其他简单类型。它也取决于你允许的操作。

所以想法是在filter.Value中保持实际类型与属性类型相同,所以它只会生成纯粹的比较

<强>更新

以下是基于您的评论的另一个示例。如果新样本具有相同类型,则会像x.Prop1 == 42(42是Filter.value)那样,或者它会进行奇怪的转换(decimal)(Convert.ChangeType((object)x.Prop1, typeof(decimal))) == (decimal)(Convert.ChangeType((object)42, typeof(decimal))),因此它会尝试转换属性中的任何类型和filter.Value为方法调用指定为<T>,然后将其用于转换。它可能会产生不需要的装箱操作,并且可能会稍微优化一下,但如果不经常操作则无关紧要。

internal class Program
{
    private static void Main(string[] args)
    {
        Filter filter = new Filter();
        filter.Operator = ">";
        filter.Value = "42";
        filter.PropertyName = "Prop1";

        ParameterExpression expr = Expression.Parameter(typeof (Test), "x");
        var resultExpr = ExpressionBuilder.GetExpression<decimal>(expr, filter);
        // and it will hold such expression:
        // {(Convert(x.Prop1) > Convert(ChangeType(Convert("42"), System.Decimal)))}

        Console.WriteLine(expr.ToString());
    }
}

public class Test
{
    public int Prop1 { get; set; }
}

public static class ExpressionBuilder
{
    private static readonly MethodInfo containsMethod = typeof (string).GetMethod("Contains");

    private static readonly MethodInfo startsWithMethod = typeof (string).GetMethod("StartsWith",
        new Type[] {typeof (string)});

    private static readonly MethodInfo endsWithMethod = typeof (string).GetMethod("EndsWith", new Type[] {typeof (string)});

    private static readonly MethodInfo changeTypeMethod = typeof (Convert).GetMethod("ChangeType",
        new Type[] {typeof (object), typeof (Type)});

    public static Expression GetExpression<T>(ParameterExpression param, Filter filter)
    {
        //the property that I access here is stored as a string
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        //this value is brought in from a web request as a string, but could be a numeric, datetime, or other type
        ConstantExpression constant = Expression.Constant(filter.Value);

        Expression targetValue;
        Expression sourceValue;

        if (filter.Value != null && member.Type == filter.Value.GetType() && member.Type == typeof(T))
        {
            targetValue = constant;
            sourceValue = member;
        }
        else
        {
            var targetType = Expression.Constant(typeof(T));

            targetValue = Convert(constant, typeof(object));
            sourceValue = Convert(member, typeof(object));

            targetValue = Expression.Call(changeTypeMethod, targetValue, targetType);
            sourceValue = Expression.Call(changeTypeMethod, sourceValue, targetType);

            targetValue = Convert(targetValue, member.Type);
            sourceValue = Convert(member, member.Type);
        }

        try
        {
            switch (filter.Operation)
            {
                case Enums.Op.Equals:
                    return Expression.Equal(sourceValue, targetValue);
                case Enums.Op.NotEqual:
                    return Expression.NotEqual(sourceValue, targetValue);
                case Enums.Op.Contains:
                    return Expression.Call(sourceValue, containsMethod, targetValue);
                case Enums.Op.StartsWith:
                    return Expression.Call(sourceValue, startsWithMethod, targetValue);
                case Enums.Op.EndsWith:
                    return Expression.Call(sourceValue, endsWithMethod, targetValue);
                case Enums.Op.GreaterThan:
                    return Expression.GreaterThan(sourceValue, targetValue);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return null;
    }

    private static Expression Convert(Expression from, Type type)
    {
        return Expression.Convert(from, type);
    }
}

public class Filter
{
    public string PropertyName { get; set; }
    public int Key { get; set; }
    public string Operator { get; set; }

    public Enums.Op Operation
    {
        get
        {
            switch (Operator.Trim())
            {
                case "<=":
                    return Enums.Op.LessThanOrEqual;
                case ">=":
                    return Enums.Op.GreaterThanOrEqual;
                case "=":
                    return Enums.Op.Equals;
                case "<":
                    return Enums.Op.LessThan;
                case ">":
                    return Enums.Op.GreaterThan;
                case "not equal to":
                    return Enums.Op.NotEqual;
                case "contains":
                    return Enums.Op.Contains;
                case "starts with":
                    return Enums.Op.StartsWith;
                case "ends with":
                    return Enums.Op.EndsWith;
                default:
                    return new Enums.Op();
            }
        }
    }

    public object Value { get; set; }
}

public class Enums
{
    public enum Op
    {

        LessThanOrEqual,
        GreaterThanOrEqual,
        Equals,
        LessThan,
        GreaterThan,
        NotEqual,
        Contains,
        StartsWith,
        EndsWith
    }
}