I'm trying to make a helper method that creates a dynamic selector using expression tree.
The objective would be converting this selector into an expression tree.
var list = new User[0].AsQueryable();
var q = list.Select(u => new { User = u, Address = "Address X", Fax = "Fax Y" });
In the expression tree visualizer it shows something like:
Here is what I have tried so far:
var userParam = Expression.Parameter(typeof(User), "u");
var obj = new { User = new User(), Address = string.Empty, Fax = string.Empty };
var q = list.Select(DynamicSelect(obj, userParam));
static Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
var user = Expression.Lambda<Func<User, User>>(/* ?? stuck here */, userParam);
var newExpression = Expression.New(
typeof(T).GetConstructor(new []{typeof(User), typeof(string), typeof(string)}),
user,
Expression.Constant("Address X"),
Expression.Constant("Fax Y"),
);
return Expression.Lambda<Func<User, T>>(newExpression, userParam);
}
You don't need a lambda inside a lambda here, you can just use userParam directly:
static Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
var newExpression = Expression.New(
typeof(T).GetConstructor(new []{typeof(User), typeof(string), typeof(string)}),
userParam,
Expression.Constant("Address X"),
Expression.Constant("Fax Y"),
);
return Expression.Lambda<Func<User, T>>(newExpression, userParam);
}
Also, I don't understand why is userParam a parameter of the method, I would create it inside the method.
Related
I am trying to create an Expression Tree for Enumerable.Any(IEnumerable, Func<T,bool>)
Actually, I have a list and need to check whether this list has at least 1 element that contains "test"
So it looks very simple:
List<string> strings = new List<string> { "element1", "element2", "element3" };
var test = strings.Any(x => x.Contains("test"));
I am trying to create an Expression Tree and GetMethod returns null, so I think I miss something
Here is a test code:
List<string> strings = new List<string> { "element1", "element2", "element3" };
var testString = "test";
ParameterExpression p = Expression.Parameter(typeof(string), "item");
Delegate predicate = Expression.Lambda(
Expression.Call(
p,
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Expression.Constant(testString)),
p).Compile();
Type predType = typeof(Func<,>).MakeGenericType(typeof(string), typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
System.Reflection.MethodInfo anyMethod = typeof(Enumerable).GetMethod("Any", new Type[] {typeof(IEnumerable<string>), predType});
Expression anyCall = Expression.Call(
anyMethod,
Expression.Constant(strings),
Expression.Constant(predicate));
// test
Func<bool> a = (Func<bool>) Expression.Lambda(anyCall).Compile();
Thanks!
Try the following:
var p = Expression.Parameter(typeof(string), "x");
var strParam = Expression.Parameter(typeof(string), "str");
// x.Contains(str)
var boyd = Expression.Call(p, nameof(string.Contains), Type.EmptyTypes, strParam);
// x => x.Contains(str)
var lambdaExpression = Expression.Lambda<Func<string, bool>>(body, p);
var enumerableParam = Expression.Parameter(typeof(IEnumerable<string>), "e");
// e.Any(x => x.Contains(str))
var anyCall = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new[]{ typeof(string) }, enumerableParam, lambdaExpression);
// e => e.Any(x => x.Contains(str))
var anyLambda = Expression.Lambda<Func<IEnumerable<string>, string, bool>>(anyCall, enumerableParam, strParam)
// Func<IEnumerable<string>, string, bool>>
var complied = anyLambda.Compile();
// test
List<string> strings = new List<string> { "element1", "element2", "element3" };
var testString = "test";
var result = complied(strings, testString);
(Disclaimer: I am the author of the library in question.)
Using the ExpressionTreeToString library, available on NuGet, you can call the ToString extension method on an expression:
List<string> strings = new List<string> { "element1", "element2", "element3" };
Expression<Func<bool>> expr = () => strings.Any(x => x.Contains("test"));
Console.WriteLine(expr.ToString("Factory methods", "C#"));
which produces output like the following:
var strings = Parameter(
typeof(List<string>),
"strings"
);
var x = Parameter(
typeof(string),
"x"
);
Lambda(
Call(
typeof(Enumerable).GetMethod("Any", new[] { typeof(IEnumerable<string>), typeof(Func<string, bool>) }),
strings,
Lambda(
Call(x,
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Constant("test")
),
x
)
)
)
and then modify it for your needs. For example, you could replace the strings variable with a ConstantExpression wrapping the list.
I am creating lambda expression for Iqueryable to get value from a collection but I want to convert that value to other datatype like int or decimal. So as I cannot use c# casting with Iqueryable so I have created user defined scalar function in sql and trying to access that in expression but it throws exception that the 'methodname' cannot be converted to sql expression.
public class Context
{
[DbFunction("dbo", "ConvertToDouble")]
public int? ConvertToDouble(string value)
{
var sql = $"set #result = dbo.[ConvertToDouble]('{value}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.Int32, Size = 16, Direction = ParameterDirection.Output };
var result = Database.ExecuteSqlCommand(sql, output);
return output.Value as int?;
}
}
private static Expression<Func<TSource, TDataType>> CreateLamdaExpression<TSource, TDataType>(string fieldName)
{
var parameterExpression = Expression.Parameter(typeof(TSource));
var collectionParameter = Expression.Property(parameterExpression, "CustomFieldValues");
var childType = collectionParameter.Type.GetGenericArguments()[0];
var propertyParameter = Expression.Parameter(childType, childType.Name);
var left = Expression.Property(propertyParameter, "Name");
var right = Expression.Constant(fieldName);
var innerLambda = Expression.Equal(left, right);
var innerFunction = Expression.Lambda(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "FirstOrDefault" && m.GetParameters().Length == 2).FirstOrDefault().MakeGenericMethod(typeof(CustomFieldValue));
var outerLambda = Expression.Call(method, Expression.Property(parameterExpression, collectionParameter.Member as System.Reflection.PropertyInfo), innerFunction);
var propertyGetter = Expression.Property(outerLambda, "Value");
if (typeof(TDataType) != typeof(object))
{
/var changeTypeCall = Expression.Call(Expression.Constant(Context), Context.GetType().GetMethod("ConvertToDouble", BindingFlags.Public | BindingFlags.Instance),
propertyGetter
);
Expression convert = Expression.Convert(changeTypeCall,
typeof(TDataType));
return Expression.Lambda<Func<TSource, TDataType>>(convert, new ParameterExpression[] { parameterExpression });
}
var result = Expression.Lambda<Func<TSource, TDataType>>(propertyGetter, new ParameterExpression[] { parameterExpression });
return result;
}
I'm new to expression Tree and I need to convert the below lambda to Expression Tree
Data.Where(s => s.Property.ToString().StartsWith("My Search Data"));
However I have done upto
Data.Where(s => s.Property.StartsWith("My Search Data");
Now I Need to use the ToString Method before Using StartsWith.
Below is my sample code.
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(data, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
query = query.Where(lambda);
Consider using the overloads that allow you to access members by name instead. It will make this significantly easier to do.
// Data.Where(s => s.Property.ToString().StartsWith("My Search Data"));
var property = "Property";
var filter = "My Search Data";
var param = Expression.Parameter(typeof(T));
var body = Expression.Call(
Expression.Call(
Expression.Property(param, property),
"ToString",
null
),
"StartsWith",
null,
Expression.Constant(filter)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
The ideea is that you have to get "ToString" method from System.Object. Because it is a virtual method, the Runtime can dispatch the call on your real object.
Note: IData is your whatever data that has a property named "Property".
ParameterExpression e = Expression.Parameter(typeof(IData), "e");
PropertyInfo propertyInfo = typeof(IData).GetProperty("Property");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
var toString = typeof (Object).GetMethod("ToString");
ConstantExpression c = Expression.Constant(data, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
var toStringValue = Expression.Call(m, toString);
Expression call = Expression.Call(toStringValue, mi, c);
Expression<Func<IData, bool>> lambda = Expression.Lambda<Func<IData, bool>>(call, e);
My LINQ query is like the below code.
var data2 = data.Where(c => String.Format("{0:MM/dd/yyyy}", c.OrderDate) == "07/04/1996");
I need to customize the predicate for formatted column in the below expression. I need to write the Expression for predicate and filter the data based on format. please check below code.
pred =Expression.Equal(membertype, Expression.Constant(value, type));
lambda = Expression.Lambda(predicate, paramExpression);
source.Where(paramExpression, predicate);
Thanks.
Here's a sample that creates the .Where dynamically.
static void DynamicWhereBuilder() {
var datas = new Data[] {
new Data { OrderDate = "07/04/1996"},
new Data { OrderDate = "07/04/1990"},
new Data { OrderDate = "07/04/2001"},
new Data { OrderDate = "2012/04/07"}
};
IQueryable<Data> queryableData = datas.AsQueryable<Data>();
var formatConstant = Expression.Constant("{0:MM/dd/yyyy}", typeof(string));
var parameter = Expression.Parameter(typeof(Data), "dataArg");
var property = Expression.Property(parameter, "OrderDate");
var left = Expression.Call(property, typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }), formatConstant, property);
var right = Expression.Constant("07/04/2001", typeof(string));
var equal = Expression.Equal(left, right);
var whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<Data, bool>>(equal, new ParameterExpression[] { parameter }));
var results = queryableData.Provider.CreateQuery<Data>(whereCallExpression); // returns the object with OrderDate = "07/04/2001"
}
I have this bit of code as an example, basically it spits out
p => p.fieldname.StartsWith("123")
But who would i expand on this to do something like this:
p => p.anotherentity.fieldname.StartsWith("123")
Here is a sample of the code i am have refactored for own needs:
string propertyName = "FirstName";
string methodName = "StartsWith";
string keyword = "123";
Type t = typeof (Person);
ParameterExpression paramExp = Expression.Parameter(t, "p");
// the parameter: p
MemberExpression memberExp = Expression.MakeMemberAccess(paramExp,
t.GetMember(propertyName).FirstOrDefault());
// part of the body: p.FirstName
MethodCallExpression callExp = Expression.Call(memberExp,
typeof (string).GetMethod(methodName,
new Type[] {typeof (string)}),
Expression.Constant(keyword));
// the body: p.FirstName.StartsWith("123")
Expression<Func<Person, bool>> whereExp = Expression.Lambda<Func<Person, bool>>(callExp, paramExp);
Expression<Func<Person, string>> selectExp = Expression.Lambda<Func<Person, string>>(memberExp, paramExp);
Console.WriteLine(whereExp); // p => p.FirstName.StartsWith("123")
Console.WriteLine(selectExp); // p => p.FirstName
To further explain let me show you what i would like to do:
public class Person
{
public string IdentityCode {get;set;}
public Loans Loans {get;set;}
}
public class Loans
{
public int Id {get;set;}
public Asset Assets {get;set;}
public Person person {get;set;}
}
public class Asset
{
public string SerialNumber {get;set;}
}
Then using an expression build something like this:
p => p.Loans.Asset.SerialNumber.StartsWith("123)
Or
p => p.Loans.Person.IdentityCode.StartsWith("123")
untested, but...
ParameterExpression paramExp = Expression.Parameter(t, "p"); // the parameter: p
MemberExpression memberExp =
Expression.MakeMemberAccess(paramExp, t.GetMember(propertyName).FirstOrDefault());
would become something like:
ParameterExpression paramExp = Expression.Parameter(t, "p"); // the parameter: p
MemberExpression otherEntityExp =
Expression.MakeMemberAccess(paramExp, t.GetMember("anotherentity").FirstOrDefault());
MemberExpression memberExp =
Expression.MakeMemberAccess(otherEntityExp, t.GetMember(propertyName).FirstOrDefault());
I'm not sure what you're asking for, updating an expression or building one from scratch...
If you already have the existing, old expression and want to update it, it would be very easy to create a new one. The idea is to dig through the expression tree down to the expression you want to replace. Then update all parent expressions with the newly replaced one.
Expression<Func<Obj, bool>> expr = p => p.fieldname.StartsWith("123");
var body = expr.Body as MethodCallExpression; // *.StartsWith()
var obj = body.Object as MemberExpression; // p.fieldname
var param = expr.Parameters.First(); // p
var newAccess = Expression.PropertyOrField(param, "anotherentity"); // p.anotherentity
var newObj = obj.Update(newAccess); // update obj
var newBody = body.Update(newObj, body.Arguments); // update body
var newExpr = expr.Update(newBody, expr.Parameters);// update expr
Otherwise to build up the expression tree:
Expression<Func<Person, bool>> expr =
p => p.Loans.Asset.SerialNumber.StartsWith("123");
Work it out from the beginning.
var p = Expression.Parameter(typeof(Person), "p");
var accessLoans = Expression.PropertyOrField(p, "Loans");
var accessAsset = Expression.PropertyOrField(accessLoans, "Asset");
var accessSerialNumber = Expression.PropertyOrField(accessAsset, "SerialNumber");
var callArgs = new Expression[] { Expression.Constant("123", typeof(string)) };
var callStartsWith = Expression.Call(accessSerialNumber, "StartsWith", null, callArgs);
var newExpr = Expression.Lambda<Func<Person, bool>>(callStartsWith, p);
I'll leave the last one as an exercise for you.