#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. Большое вам спасибо! Я сам начал изменять класс, но мне потребовалось бы больше времени, чтобы понять, что все делает.