Generate a query from an Expression in C#
I'm exploring ideas on how to improve my interactions with various components in my personal system(s). One of the things I'd always wanted to try is using an existing class and generating a query string from it.
Using the built-in Expression
s for that has several advantages - it ensures type safety, is easy to use and idiomatic, and could be used in a more complex use-case, like a custom LINQ provider. (Though I will probably not go that far. Seems like overkill.)
After playing around in LinqPad, I have been able to get the basics working. This has been one of those exercises where figuring out what to use was more difficult than doing it.
I have built a Parser
class, because it parses the expression tree. Not the greatest name, but good enough.
I have a small Note
class:
public class Note
{
public string Name { get; set; }
public string Id { get; set; }
public int Order { get; set; }
public bool IsTruth { get; set; }
public static string Query(
Expression<Func<Note, bool>> predicate)
{
return predicate.Body.Parse();
}
}
It is used like this:
void Main()
{
var queryStr = Note.Query(
x => x.Name == "jano"
|| x.Name == "fero"
&& x.Id == "123"
&& x.Order == 1337
|| x.IsTruth == false);
queryStr.Dump();
}
This dumps this string representation:
Name = "jano" OR Name = "fero" AND Id = "123" AND Order = 1337 OR IsTruth = false
I have kept spaces and uppercase operators to make it more readable, but it would be trivial to replace them and generate a valid query string, which could be sent to an API. This is the Parser class itself:
public static class Parser
{
public static string Parse(this Expression ex)
{
if (ex is BinaryExpression)
return ((BinaryExpression)ex).Parse();
if (ex is MemberExpression)
return ((MemberExpression)ex).Parse();
if (ex is ConstantExpression )
return ((ConstantExpression)ex).Parse();
return "?? " + ex.GetType();
}
static string Parse(this BinaryExpression ex)
{
return $"{ex.Left.Parse()} {ex.NodeType.Operation()} {ex.Right.Parse()}";
}
static string Parse(this MemberExpression ex)
{
return $"{ex.Member.Name}";
}
static string Parse(this ConstantExpression ex)
{
if (ex.Type == typeof(string))
return $"\"{ex.Value}\"";
if (ex.Type == typeof(int)
|| ex.Type == typeof(long))
return ex.Value.ToString();
if (ex.Type == typeof(bool))
return ex.Value.ToString().ToLower();
return $"Unknown type {ex.Type}";
}
static string Operation(this ExpressionType op)
=> op.ToString() switch {
"Equal" => "=",
"Or" => "OR",
"OrElse" => "OR",
"And" => "AND",
"AndAlso" => "AND",
_ => "UNKNOWN " + op.ToString()
};
}
It is by no means complete - for example, it doesn't handle parentheses in any way. But it's good enough to let me see whether this is something I might want to actually use.
Comments ()