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

本文关键字:动态 LINQ 字符串 修改 验证 | 更新日期: 2023-09-27 18:08:26

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

有人有更好的主意吗?

谢谢!

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

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

我这样说的原因是因为如果你试图解析像it.Date >= "1/1/2014"这样的表达式,DynamicLinq将尝试在DateTime属性和string之间构建>=比较,这将失败,因为没有这样的操作符存在。这有效地防止了您在事后重写表达式树,因为DynamicLinq将无法构建它。

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


解决方案1:自定义日期时间文本

我刚刚对DynamicLinq做了一个快速的概念修改证明,允许DateTime文字与#符号引用,例如,#6/13/2014#。这很简单:

TokenId enum中添加DateTimeLiteral表项。

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