How to Convert a String to Its Equivalent Linq Expression Tree

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



Leave a reply



Submit