LINQ 表达式生成获取属性并转换为类型

本文关键字:转换 类型 属性 获取 表达式 LINQ | 更新日期: 2023-09-27 18:37:13

我正在尝试根据此处的CodeProject文章过滤数据,但是对于不是字符串的值,我遇到了困难。 这是我现在正在做的事情:

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; }
    }

当我尝试根据数字进行过滤时,例如(值 <12),我得到一个异常,您无法将对象与小数进行比较。 转换到类型实际上没有将值从字符串转换为小数吗?

提前感谢!

LINQ 表达式生成获取属性并转换为类型

在您的情况下,最简单的方法是将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、字符串和其他简单类型。这也取决于您允许的操作。

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

更新

这是根据您的评论的另一个示例。如果新样本具有相同的类型,则新示例将像x.Prop1 == 42(42 是 Filter.value)一样执行,或者它会执行奇怪的转换(decimal)(Convert.ChangeType((object)x.Prop1, typeof(decimal))) == (decimal)(Convert.ChangeType((object)42, typeof(decimal))),因此它将尝试转换属性和过滤器中的任何类型。值指定为方法调用的<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
    }
}