How to convert a String to its equivalent LINQ Expression Tree?
Would the dynamic linq library help here? In particular, I'm thinking as a Where
clause. If necessary, put it inside a list/array just to call .Where(string)
on it! i.e.
var people = new List<Person> { person };
int match = people.Where(filter).Any();
If not, writing a parser (using Expression
under the hood) isn't hugely taxing - I wrote one similar (although I don't think I have the source) in my train commute just before xmas...
How can I convert string to linq lambda expression in C#
It is possible to create string predicate with Dynamic Linq. Unfortunately, I am not familiar with it enough to tell how to iterate over a nested dictionary, so I use combined approach of a simple predicate $"Key == @0 && Value == @1"
and foreach
loop. You might want to learn more about this nuget package to get rid of foreach
loop.
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
namespace DynamicLinqDemo
{
internal class Program
{
static void Main(string[] args)
{
var item = GetDictionaryItem();
}
public static Dictionary<string, string> GetDictionaryItem()
{
var dict = new List<Dictionary<string, string>>()
{
new Dictionary<string, string>()
{
{"a","1-1" },
{"b","1-2" }
},
new Dictionary<string, string>()
{
{"a","1-3" },
{"b","1-4" }
}
};
var Field = "a";
var Context = "1-1";
string predicate = $"Key == @0 && Value == @1";
foreach (var item in dict)
{
var result = item.AsQueryable().Where(predicate, Field, Context).ToList();
if (result.Count > 0)
return item;
}
return null;
}
}
}
Convert Contains To Expression Tree
a.Address.Contains(strToCheck)
represents a call to string.Contains
instance method on a.Address
instance with strToCheck
argument.
The simplest way to build the corresponding expression is to use the following Expression.Call
overload:
public static MethodCallExpression Call(
Expression instance,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
like this (using the terms from the linked question):
var body = Expression.Call(
Expression.PropertyOrField(param, "Address"), // instance
"Contains", // method
Type.EmptyTypes, // no generic type arguments
Expression.Constant(strToCheck) // argument
);
Convert List.Contains to Expression Tree
The problem is that you have switched two arguments to Expression.Call
, your code is trying to create the nonsensical expression o.Status.Contains(lst)
.
You need to switch the two arguments around:
Expression.Call(Expression.Constant(lst),
"Contains",
Type.EmptyTypes,
Expression.PropertyOrField(param, "Status"))
This is assuming that the LINQ provider you're using understands List<T>.Contains()
. If you need Enumerable.Contains()
, then have a look at Ivan Stoev's answer.
Can't convert this linq to dynamic expression tree
You don't need to construct the Expression.Property for simple types like string.
For example. If I have to build expression tree for OrderBy method for type Person
with Name
property, I'll build the expression tree like this:
ParameterExpression pe = System.Linq.Expressions.Expression.Parameter(typeof(T), "p");
Expression<Func<T, TPropertyType>> expr = System.Linq.Expressions.Expression.Lambda<Func<T, TPropertyType>>(System.Linq.Expressions.Expression.Property(pe, propertyName), pe);
But for string type I'll simply do : (Since your expression will simply be x=>x for string types)
If( typeof(T)==typeof(string))
{
ParameterExpression pe = System.Linq.Expressions.Expression.Parameter(typeof(T), "p");
Expression<Func<T, TPropertyType>> expr = System.Linq.Expressions.Expression.Lambda<Func<T, TPropertyType>>(pe,pe);
}
You can probably use the same concept to fix your problem.
transform formula given as a string into expression tree and apply to array
I would consider almost anything in .NET not the most efficient by default, but the most efficient in C# would probably be unsafe
code and parsing the expression yourself.
The easiest way that I know of is with DataColumn.Expression
:
var dt = new DataTable();
dt.Columns.Add("colum1", typeof(double));
dt.Columns.Add("colum2", typeof(double));
dt.Columns.Add("result", typeof(double), "colum1 * colum2");
var dr = dt.Rows.Add(1.3, 2);
Debug.Print($"{dr[0]} * {dr[1]} = {dr[2]}"); // "1.3 * 2 = 2.6"
Convert this LINQ to dynamic Expression tree
Basically you should use the following Expression.Call
overload which allows you to build an expression for calling static generic methods (what are all the LINQ extension methods).
To build the equivalent of expression like this
m => m.MyChildren.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate
you can use the following snippet:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
var firstOrDefaultCall = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { childType },
orderByDescendingCall
);
propertyGetter = Expression.Property(firstOrDefaultCall, childProp);
But note that you'll get NRE if the collection is empty.
So you'd better build an expression like this:
m => m.MyChildren.OrderByDescending(c => c.SavedDate)
.Select(c => (DateTime?)c.SavedDate).FirstOrDefault()
with:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
Expression propertySelector = propertyAccess;
// If value type property and not nullable, convert it to nullable
if (propertySelector.Type.IsValueType && Nullable.GetUnderlyingType(propertySelector.Type) == null)
propertySelector = Expression.Convert(propertySelector, typeof(Nullable<>).MakeGenericType(propertySelector.Type));
var selectCall = Expression.Call(
typeof(Enumerable), "Select", new Type[] { childType, propertySelector.Type },
orderByDescendingCall, Expression.Lambda(propertySelector, childInParam)
);
propertyGetter = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { propertySelector.Type },
selectCall
);
Related Topics
How to Split a String by a Multi-Character Delimiter in C#
How to Parse Huge JSON File as Stream in JSON.Net
Up, Down, Left and Right Arrow Keys Do Not Trigger Keydown Event
How to Get Text Formatting with Itextsharp
C# Download All Files and Subdirectories Through Ftp
How to Represent 0.1 in Floating Point Arithmetic and Decimal
How to Use a C# Keyword as a Property Name
Can't View Designer When Coding a Form in C#
How to Deserialize a Child Object with Dynamic (Numeric) Key Names
What Are the Differences Between Delegates and Events
How to Inject JavaScript in Webbrowser Control
How to Create a Simple Proxy in C#
Detect Windows Version in .Net
How to Get Object Size in Memory