我使用System.Linq.Dynamic命名空间构建基于字符串的谓词,我想在用户提供的字符串中添加一些额外的验证和格式。更具体地说,我试图避免用户在想要通过识别数据类型在字符串中提供日期时键入ToDate(" 2014/06/13")他要比较的参数是DateTime,并在日期周围注入DateTime()字符串。
从字符串中获取Lambda的代码:
var p = Expression.Parameter(typeof(Customer), "Customer");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda<Customer, bool>(customFilter, p);
我想到的唯一想法是分析表达式的DebugView,获取属性,以某种方式通过反射将它们转换为类型并从那里做我的逻辑。但它似乎有点复杂。 DebugView中:
.Lambda #Lambda1<System.Func`2[Customer,System.Boolean]>(Customer $var1)
{
.Call ($var1.CompanyName).StartsWith("S") || $var1.AttrCount >= 3 && $var1.ConnectionsCount >= 0
}
有没有人有更好的主意?
谢谢!
答案 0 :(得分:2)
您可能需要预处理字符串或扩展DynamicLinq以添加对日期/时间文字的支持。后者可能是一个更好的选择,因为解析器已经写好了;你只需要扩展它。
我说这是因为如果您尝试解析it.Date >= "1/1/2014"
这样的表达式,DynamicLinq将尝试在>=
属性与DateTime
之间构建string
比较},这将失败,因为不存在这样的运算符。这有效地阻止了您在事后重写表达式树,因为DynamicLinq无法构造它。
我在下面提供了几个概念验证解决方案,用于扩展DynamicLinq。我个人更喜欢第一种解决方案,但第二种解决方案更符合你原来的问题。
我刚刚对DynamicLinq进行了概念修改的快速证明,允许DateTime
文字引用#
个符号,例如#6/13/2014#
。这很简单:
将DateTimeLiteral
条目添加到TokenId
枚举。
将以下内容添加到ExpressionParser.NextToken()
中的开关:
case '#':
NextChar();
while (textPos < textLen && ch != '#') NextChar();
if (textPos == textLen)
throw ParseError(textPos, Res.UnterminatedDateTimeLiteral);
NextChar();
t = TokenId.DateTimeLiteral;
break;
将以下内容添加到ExpressionParser.ParsePrimaryStart()
中的开关:
case TokenId.DateTimeLiteral:
return ParseDateTimeLiteral();
将此方法添加到ExpressionParser
:
Expression ParseDateTimeLiteral() {
ValidateToken(TokenId.DateTimeLiteral);
string s = token.text.Substring(1, token.text.Length - 2);
//
// I used InvariantCulture to force a consistent set of formatting rules.
//
DateTime d = DateTime.Parse(s, CultureInfo.InvariantCulture);
NextToken();
return Expression.Constant(d);
}
将此条目添加到Res
类:
public const string UnterminatedDateTimeLiteral = "Unterminated DateTime literal";
如果您不想使用DateTime
文字的特殊语法,可以按如下方式修改ExpressionParser.ParseComparison()
,以便只检测string
何时与DateTime
进行比较},并解析该点的日期:
else if (IsEnumType(left.Type) || IsEnumType(right.Type)) {
// existing code here
}
else {
//
// Begin added code
//
if (IsDateTime(left.Type) && IsStringLiteral(right) ||
IsStringLiteral(left) && IsDateTime(right.Type))
{
if (left.Type == typeof(string))
left = Expression.Constant(DateTime.Parse((string)((ConstantExpression)left).Value, CultureInfo.InvariantCulture));
else
right = Expression.Constant(DateTime.Parse((string)((ConstantExpression)right).Value, CultureInfo.InvariantCulture));
}
//
// End added code
//
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures),
op.text, ref left, ref right, op.pos);
}
并添加以下方法:
static bool IsDateTime(Type type) {
return GetNonNullableType(type) == typeof(DateTime);
}
static bool IsStringLiteral(Expression e) {
var c = e as ConstantExpression;
return c != null && c.Value is string;
}