0

我有自定义查询类,它将用于在 lambda 表达式的帮助下构建查询,如下所示

var query = new Query("Person").Where<Person>(p =>
(p.Name == "Maulik" && (p.Id == 2 || p.Age == 30)) || (p.Id == 2 && p.Name == "Maulik"));

通过ExpressionVisitor基于此参考链接类实现,我能够将翻译后的查询转换为字符串格式,如下所示

(((Name EqualTo "Maulik") AND ((Id EqualTo 2) OR (Age EqualTo 30))) OR ((Id EqualTo 2) AND (Name EqualTo 'Maulik')))

但是,我想将上面的表达式转换为我的自定义嵌套类

public class Expression
{
    public List<Expression> Filters { get; } // Nested expression
    public Operator Operator { get; set; } //e.g AND/OR
    public List<Condition> Conditions { get; } // One Expression can have many conditions
}

public class Condition
{
    public string Name { get; set; }
    public Operator Operator { get; set; } //e.g <,> ==, != etc
    public object Value { get; set; }
}

其中 Expression 表示:(A > 5 && A < 10)和 Condition 表示最内在的标准A > 5

嵌套的意思是,一个表达式可以有子表达式,也可以有子子表达式等等,每个表达式可以有多个条件。

转换成这种结构的原因是为了处理各种 SQL/NoSQL 提供者,从这种类结构中将为所有不同的提供者创建单独的查询,因此不能完全改变这种类结构。

创建这个类结构是为了维护基于查询的AND和OR条件的括号顺序,因此同级条件可以用单个表达式来俱乐部。

我正在寻找任何可以帮助将表达式转换为嵌套类结构的通用映射。

将表达式转换为自定义嵌套类结构的最佳方法是什么?如果有任何可用的实施示例,请分享。我还找不到任何相关的例子。

4

1 回答 1

0

我认为您的课程没有正确反映表达式的结构。让我使用EBNF写下此类表达式的可能语法(不要求完整性):

Expression = Term { ("AND" | "OR") Term }.
Term = "(" Expression ")" | Comparison.
Comparison = name ("=" | "!=" | "<" ...) Value.
Value = stringConstant | number.

我们看到的一件重要的事情是 aTerm可以是 anExpression或 a ComparisonAND这意味着我们必须能够将一个或另一个插入到 or 的两侧OR。示例:Name = "Maulik" AND (Id = 2 OR Id = 3)。左边AND是比较,右边是表达式。

因此,我们必须使这两个事物的赋值兼容,例如,通过从同一个基类派生它们。

public abstract class Term
{
}

public Expression : Term
{
    public Term FirstTerm { get; set; }
    public List<(BooleanOperator operator, Term term)> SucceedingTerms { get; } = new();
}

public Comparison : Term
{
    public string Name { get; set; }
    public ComparisonOperator Operator { get; set; }
    public object Value { get; set; }
}

另一个重要的事实是语法是递归的。即,一个表达式包含术语,而术语可以包含另一个表达式。这对于能够表示具有多级嵌套的括号表达式是必要的。

语法的这种递归性质有两个后果:

  1. 类结构必须允许递归结构。这是因为Expression该类有一个包含Terms 的列表,而这些列表又可以是Expressions。
  2. 后面显示的解析器必须是递归的。它包含间接递归调用(ParseExpression调用ParseTermParseTerm调用ParseExpression)。

请注意,我使用ValueTulpes作为由运算符和术语组成的列表元素。


现在,到转换本身。你需要一个编译器。任何一个


由于语法简单,您可能敢于自己编写编译器。

将任务分为词法分析(由词法分析器完成)和语法分析(由解析器完成)。除了分析语法外,解析器还创建所需的数据结构作为输出。词法分析器只返回一个符号流。例如

enum Symbol { EndOfInput, LeftPar, RightPar, AndOperator, OrOperator, Identifier, Number,
              StringLiteral, ...}
private string _input;
private int _currentPos;
private string _identifier;
private string _stringValue;
private decimal _number;
private Symbol _symbol;

/// Does the lexical analysis.
private void GetSymbol()
{
    // Get next `_symbol` (and associated variables) from `_input` at `_currentPos` and
    // update `_currentPos`.
}

使用此基本基础架构,您可以创建解析器。解析器由密切反映语法结构的方法组成(如上面的 EBNF 中给出的)。

// Expression = Term { ("AND" | "OR") Term }.
void Expression ParseExpression()
{
    var expression = new Expression();
    expression.FirstTerm = ParseTerm();
    while (_symbol == Symbol.AndOperator || _symbol == Symbol.OrOperator) {
        var op = _symbol == Symbol.AndOperator
            ? BooleanOperator.And
            : BooleanOperator.Or;
        GetSymbol();
        term = ParseTerm();
        expression.SucceedingTerms.Add((op, term));
    }
    return expression;
}

// Term = "(" Expression ")" | Comparison.
void Term ParseTerm()
{
    Term term = null;
    if (Symbol == Symbol.LeftPar) {
        GetSymbol();
        term = ParseExpression();
        if (Symbol == Symbol.RightPar) {
            GetSymbol();
        } else {
            Error("\")\" expected.");
        }
    } else {
        term = ParseComparison();
    }
    return term;
}

这并不完整,但你明白了。由于此解析器的结构取决于语法产生 (EBNF),因此在确定确切的语法之前不要从它开始。

请注意在下一个符号给出的语法中始终具有可确定的路径。首先,我错了。我的任期由Term = "(" Expression | Comparison ")" | Comparison.. 这里的问题是一个表达式以一个术语开头,而一个术语可以是一个比较。比较从名称开始。因此,两者都Expression可以Comparison以名称开头。当解析 Expression | Comparison并且下一个符号是名称时,我们无法决定是否必须解析表达式或比较。

更新的语法是Term = "(" Expression ")" | Comparison.. 现在,我们知道如果下一个符号是左括号,我们必须解析表达式,否则解析比较。

于 2021-06-25T14:52:25.093 回答