验证并修改传递给动态LINQ的字符串

时间:2014-06-13 14:16:07

标签: c# linq dynamic

我使用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
}

有没有人有更好的主意?

谢谢!

1 个答案:

答案 0 :(得分:2)

您可能需要预处理字符串或扩展DynamicLinq以添加对日期/时间文字的支持。后者可能是一个更好的选择,因为解析器已经写好了;你只需要扩展它。

我说这是因为如果您尝试解析it.Date >= "1/1/2014"这样的表达式,DynamicLinq将尝试在>=属性与DateTime之间构建string比较},这将失败,因为不存在这样的运算符。这有效地阻止了您在事后重写表达式树,因为DynamicLinq无法构造它。

我在下面提供了几个概念验证解决方案,用于扩展DynamicLinq。我个人更喜欢第一种解决方案,但第二种解决方案更符合你原来的问题。


解决方案1:自定义DateTime文字

我刚刚对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";

解决方案2:在比较中将字符串隐式转换为DateTime

如果您不想使用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;
}