我正在尝试根据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实际上是不是将字符串中的值转换为小数?
提前致谢!
答案 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
}
}