Проверка и изменение строки, передаваемой в динамический LINQ

#c# #linq #dynamic

#c# #linq #динамический

Вопрос:

Я создаю предикат на основе строки с помощью System.Linq .Динамическое пространство имен, и я хотел бы добавить некоторые дополнительные проверки и форматирование к строке, предоставленной пользователем. Чтобы быть более конкретным, я пытаюсь избежать того, чтобы пользователю приходилось вводить ToDate(«2014/06/13»), когда он хочет указать дату в строке, указав, что тип данных аргумента, который он хочет сравнить, — DateTime, и вводит строку DateTime() вокруг даты.

Код для получения лямбда-выражения из строки:

  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 amp;amp; $var1.ConnectionsCount >= 0
}
  

У кого-нибудь есть идея получше?

Спасибо!

Комментарии:

1. Где именно ToDate() определена эта функция? Будет ли DynamicLinq даже анализировать это?

2. Ups… это DateTime(‘строка’).

Ответ №1:

Вероятно, вам придется предварительно обработать строку или расширить DynamicLinq, чтобы добавить поддержку литералов даты / времени. Последнее, вероятно, лучший выбор, поскольку анализатор уже написан; вам просто нужно его расширить.

Причина, по которой я говорю это, заключается в том, что если вы попытаетесь проанализировать выражение like it.Date >= "1/1/2014" , DynamicLinq попытается построить >= сравнение между DateTime свойством и a string , которое завершится неудачей, поскольку такого оператора не существует. Это эффективно предотвращает перезапись дерева выражений после факта, поскольку DynamicLinq не сможет его построить.

Ниже я включил несколько концептуальных решений для расширения DynamicLinq. Я лично предпочитаю первое решение, но второе более точно соответствует вашему первоначальному вопросу.


Решение 1: Пользовательские литералы даты и времени

Я только что сделал быстрое доказательство концептуальной модификации DynamicLinq, которая позволяет DateTime заключать литералы в кавычки с # символами, например, #6/13/2014# . Это было довольно просто:

Добавьте DateTimeLiteral запись в TokenId перечисление.

Добавьте следующее к переключателю в ExpressionParser.NextToken() :

 case '#':
    NextChar();
    while (textPos < textLen amp;amp; 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() следующим образом, чтобы просто определять, когда a string сравнивается с a DateTime , и анализировать дату в этот момент:

 else if (IsEnumType(left.Type) || IsEnumType(right.Type)) {
    // existing code here
}
else {
    //
    // Begin added code
    //
    if (IsDateTime(left.Type) amp;amp; IsStringLiteral(right) || 
        IsStringLiteral(left) amp;amp; 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 amp;amp; c.Value is string;
}
  

Комментарии:

1. То, что вы предлагаете, звучит логично. К сожалению, я понятия не имею, как расширить анализатор DynamicLinq. Я изучу это и, вероятно, задам еще один вопрос сообществу на случай, если я не пойму этого. Спасибо!

2. Я сейчас просматриваю исходные тексты, и это всего лишь один большой файл C #. Должно быть довольно просто.

3. Поскольку у меня были под рукой исходные тексты, я попытался добавить литералы даты / времени самостоятельно, и это заняло очень мало времени. Я включил изменения в свой пост.

4. Большое вам спасибо! Я сам начал изменять класс, но мне потребовалось бы больше времени, чтобы понять, что все делает.