Translating C# Lambda Expressions to General Purpose Filter Descriptors & HTTP Query Strings by Kamran Saeedi
Kamran Saeedi - 22 May 2020
Kamran Saeedi - 22 May 2020
[SHOWTOGROUPS=4,20]
An idea on how to use .NET ExpressionVisitor to translate lambda expressions into classes that encapsulate data suitable for filtering data & creating query strings
C# lambda expressions provide a convenient and concise way of describing a condition for filtering almost any type of data in a C# program. But, converting a given lambda expression to other forms used in other domains, such as an HTTP request, can be tedious and tricky. This article aims to provide a way for this issue using ExpressionVisitor class of .NET framework.
It is relatively common to build a mechanism for converting business conditions expressed in code to a format usable by other tiers of a solution, be it a database or a Web service, especially in the infrastructure layers of a solution. Either of the two following common scenarios is an example of such case:
или Зарегистрируйся class in Для просмотра ссылки Войди или Зарегистрируйся namespace is an excellent tool for inspecting, modifying and translating lambda expressions.
In this article, we mainly use Для просмотра ссылки Войдиили Зарегистрируйся class to propose a solution to the first scenario above.
Background
Before diving into the details, let us have a very brief introduction to the general concept of expressions, then the condition expressions as a more special type, and finally a very short description of Для просмотра ссылки Войдиили Зарегистрируйся class. It will be very short but absolutely necessary, so please skip this section only if you know these subjects beforehand.
What Are Expressions in General and How Are Condition Expressions Different From Them?
Expressions in general represent a delegate or method. An expression itself is not a delegate or method. It represents a delegate or method, i.e., an expression defines a delegate's structure. In .NET platform, we use Expression class to define an expression. However, before defining its delegate's body, it is necessary to define the signature of the delegate it is going to represent. This signature is given to the Expression class via a generic type parameter named TDelegate.
Therefore, the form of the expression class is Для просмотра ссылки Войдиили Зарегистрируйся.
Having this in mind, it is obvious that a condition expression represents a delegate that takes an object of an arbitrary type T as the input and returns a Boolean value. As a result, the delegate of a condition expression will be of type Func<T, bool>, hence Expression<Func<T, bool>> the type of the condition expression.
How ExpressionVistor Works
We usually use a lambda expression to define an expression. A lambda expression consists of multiple different expressions combined together. Consider this example lambda:
The below figure marks different parts of it:
As you can see, this expression is a combination of some other expressions and operators.
Now let us see how ExpressionVisitor treats an expression like the one above. This class implements visitor pattern. Its main method, or entry point, called Visit is a dispatcher that calls several other specialized methods. When an expression is passed to the Visit method, the expression tree is traversed and depending on the type of each node, a specialized method is called to visit (inspect and modify) that node, and its children, if any. Inside each method, if the expression is modified, the modified copy of it will be returned; otherwise, the original expression. Please keep in mind that expressions are immutable and any modification would result in a new instance being built and returned.
In Microsoft’s Для просмотра ссылки Войдиили Зарегистрируйсяfor .NET framework 4.8,Для просмотра ссылки Войди или Зарегистрируйся35 special visit methods are documented. A few interesting ones that are used in our solution are listed here:
или Зарегистрируйся should override the necessary ones and implement its own logic. This is how a custom visitor is built.
For those readers who might be willing to obtain a good understanding of how our solution works, having at least a minimum familiarity with the following subjects is necessary.
As the below figure shows, we have a FilterBuilder class that takes an expression of type Expression<Func<T, bool>> as the input. This class is the main part of the solution. At the first step, the FilterBuilder examines the input expression and outputs a collection of FilterDescriptors (IEnumerable<FilterDescriptor>). In the next step, a converter, converts this collection of FilterDescriptors to a desired form, e.g., query string key-value pairs to be used in an HTTP request, or a string to be used as a SQL WHERE clause. For every type of conversion, a separate converter is needed.
One question may arise here: why do we not convert the input expression directly to a query string? Is it necessary to take on the burden of generating FilterDescriptors? Can this extra step be skipped? The answer is if all you need is to generate query strings and no more than that, and if you are not looking for a general solution, you are free to do so. However, this way, you will end up having a very specific Для просмотра ссылки Войдиили Зарегистрируйся suitable for only one type of output. For that goal, a good article is written Для просмотра ссылки Войди или Зарегистрируйся. However, what this article tries to do is exactly the opposite: proposing a more general solution.
The Solution
Foundations
At the heart of our solution is the FilterBuilder class, which inherits from Для просмотра ссылки Войдиили Зарегистрируйся. The constructor of this class takes an expression of type Expresion<Func<T, bool>>. This class has a public method named Build that returns a collection of FilterDescriptor objects. FiterDescriptor is defined as follows:
Type of the FilterOperator property of this class is an enumeration. This property specifies the operator of the filter.
Expressions nodes are not directly converted to FilterDescriptor objects. Instead, each overridden method that visits an expression node, creates an object named token and adds it to a private list. Tokens in this list are arranged according to Для просмотра ссылки Войдиили Зарегистрируйся.
[/SHOWTOGROUPS]
An idea on how to use .NET ExpressionVisitor to translate lambda expressions into classes that encapsulate data suitable for filtering data & creating query strings
C# lambda expressions provide a convenient and concise way of describing a condition for filtering almost any type of data in a C# program. But, converting a given lambda expression to other forms used in other domains, such as an HTTP request, can be tedious and tricky. This article aims to provide a way for this issue using ExpressionVisitor class of .NET framework.
- .../KB/recipes/5260863/FilterExpression.zip
- https://dumpz.ws/resources/translat...se-filter-descriptors-http-query-strings.104/
It is relatively common to build a mechanism for converting business conditions expressed in code to a format usable by other tiers of a solution, be it a database or a Web service, especially in the infrastructure layers of a solution. Either of the two following common scenarios is an example of such case:
- Suppose we want to pass filter conditions from inside a C# client to an HTTP service. These conditions can be sent in a query string collection, but manually constructing query strings through string concatenation, not only does not seem nice and clean, but would highly likely be hard to debug and maintain.
- There can be times that we might want to translate filter conditions into SQL WHERE clauses without using an ORM tool. Again, constructing SQL WHERE clauses for database queries through manual string manipulation seems error prone and hard to maintain.
In this article, we mainly use Для просмотра ссылки Войди
Background
Before diving into the details, let us have a very brief introduction to the general concept of expressions, then the condition expressions as a more special type, and finally a very short description of Для просмотра ссылки Войди
What Are Expressions in General and How Are Condition Expressions Different From Them?
Expressions in general represent a delegate or method. An expression itself is not a delegate or method. It represents a delegate or method, i.e., an expression defines a delegate's structure. In .NET platform, we use Expression class to define an expression. However, before defining its delegate's body, it is necessary to define the signature of the delegate it is going to represent. This signature is given to the Expression class via a generic type parameter named TDelegate.
Therefore, the form of the expression class is Для просмотра ссылки Войди
Having this in mind, it is obvious that a condition expression represents a delegate that takes an object of an arbitrary type T as the input and returns a Boolean value. As a result, the delegate of a condition expression will be of type Func<T, bool>, hence Expression<Func<T, bool>> the type of the condition expression.
How ExpressionVistor Works
We usually use a lambda expression to define an expression. A lambda expression consists of multiple different expressions combined together. Consider this example lambda:
Код:
p => p.Price < 1000 && p.Name.StartsWith("a-string") && !p.OutOfStock
The below figure marks different parts of it:
As you can see, this expression is a combination of some other expressions and operators.
Now let us see how ExpressionVisitor treats an expression like the one above. This class implements visitor pattern. Its main method, or entry point, called Visit is a dispatcher that calls several other specialized methods. When an expression is passed to the Visit method, the expression tree is traversed and depending on the type of each node, a specialized method is called to visit (inspect and modify) that node, and its children, if any. Inside each method, if the expression is modified, the modified copy of it will be returned; otherwise, the original expression. Please keep in mind that expressions are immutable and any modification would result in a new instance being built and returned.
In Microsoft’s Для просмотра ссылки Войди
- Для просмотра ссылки Войди
или Зарегистрируйся visits the Для просмотра ссылки Войдиили Зарегистрируйся. - Для просмотра ссылки Войди
или Зарегистрируйся visits the children of the Для просмотра ссылки Войдиили Зарегистрируйся. - Для просмотра ссылки Войди
или Зарегистрируйся visits the children of the Для просмотра ссылки Войдиили Зарегистрируйся. - Для просмотра ссылки Войди
или Зарегистрируйся visits the children of the Для просмотра ссылки Войдиили Зарегистрируйся. - Для просмотра ссылки Войди
или Зарегистрируйся visits the children of the Для просмотра ссылки Войдиили Зарегистрируйся. - Для просмотра ссылки Войди
или Зарегистрируйся visits the children of a Для просмотра ссылки Войдиили Зарегистрируйся.
For those readers who might be willing to obtain a good understanding of how our solution works, having at least a minimum familiarity with the following subjects is necessary.
- Expression Trees (1) & (2)
- A general concept behind the lambda expressions that we want to translate
- Tree Traversals (Inorder, Preorder and Postorder)
- Algorithms used to iterate a tree
- Visitor Design Pattern
- A design pattern that is used to parse expression trees
- ExpressionVisitor Class
- A class provided by Microsoft .NET platform that uses visitor design pattern to expose methods for inspecting, modifying and translating expression trees. We will be using these methods to inspect each node of the interest in the tree and extract needed data from it.
- Reverse Polish Notation (RPN)
- In reverse Polish notation, the operators follow their operands; for instance, to add 3 and 4, one would write "3 4 +" rather than "3 + 4".
As the below figure shows, we have a FilterBuilder class that takes an expression of type Expression<Func<T, bool>> as the input. This class is the main part of the solution. At the first step, the FilterBuilder examines the input expression and outputs a collection of FilterDescriptors (IEnumerable<FilterDescriptor>). In the next step, a converter, converts this collection of FilterDescriptors to a desired form, e.g., query string key-value pairs to be used in an HTTP request, or a string to be used as a SQL WHERE clause. For every type of conversion, a separate converter is needed.
One question may arise here: why do we not convert the input expression directly to a query string? Is it necessary to take on the burden of generating FilterDescriptors? Can this extra step be skipped? The answer is if all you need is to generate query strings and no more than that, and if you are not looking for a general solution, you are free to do so. However, this way, you will end up having a very specific Для просмотра ссылки Войди
The Solution
Foundations
At the heart of our solution is the FilterBuilder class, which inherits from Для просмотра ссылки Войди
Код:
public class FilterDescriptor
{
public FilterDescriptor()
{
CompositionOperator = FilterOperator.And;
}
private FilterOperator _compositionOperator;
public FilterOperator CompositionOperator
{
get => _compositionOperator;
set
{
if (value != FilterOperator.And && value != FilterOperator.Or)
throw new ArgumentOutOfRangeException();
_compositionOperator = value;
}
}
public string FieldName { get; set; }
public object Value { get; set; }
public FilterOperator Operator { get; set; }
// For demo purposes
public override string ToString()
{
return
$"{CompositionOperator} {FieldName ?? "FieldName"} {Operator} {Value ?? "Value"}";
}
}
Type of the FilterOperator property of this class is an enumeration. This property specifies the operator of the filter.
Код:
public enum FilterOperator
{
NOT_SET,
// Logical
And,
Or,
Not,
// Comparison
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
// String
StartsWith,
Contains,
EndsWith,
NotStartsWith,
NotContains,
NotEndsWith
}
Expressions nodes are not directly converted to FilterDescriptor objects. Instead, each overridden method that visits an expression node, creates an object named token and adds it to a private list. Tokens in this list are arranged according to Для просмотра ссылки Войди
[/SHOWTOGROUPS]
Последнее редактирование: