c# Expression Lambda to Func<T> from string - c#

I try to implement e dynamic Funx expression from string
Expression<Func<CustomerDto, object>> expr = (src) => src.Customer.Id;
Func<CustomerDto, object> delg = expr.Compile();
var id = delg.Invoke(customerListDtos[0]);
and return the id (es. 123)
So now I try to create expression from string
public Expression<Func<T, object>> GetLambda<T>(string property)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
//return Expression.Lambda(body, param);
//Expression parent = Expression.Property(param, property);
Expression parent = Expression.Lambda(body, param);
//if (!parent.Type.IsValueType)
//{
// return Expression.Lambda<Func<T, object>>(parent, param);
//}
var convert = Expression.Convert(parent, typeof(object));
return Expression.Lambda<Func<T, object>>(convert, param);
}
So if I call the result from GetLambda the output is not 123 but is
var data = GetLambda<CustomerDto>("Customer.Id");
Func<CustomerDto, object> g = data.Compile();
var id = g(customerListDtos[0]);
the result is
{Method = {Int64 lambda_method(System.Runtime.CompilerServices.Closure, ef.Customer.CustomerDto)}}

You are calling Expression.Labmda twice, essentially wrapping one func into another. Try this instead:
public static Expression<Func<T, object>> GetLambda<T>(string property) {
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.')) {
body = Expression.PropertyOrField(body, member);
}
var convert = Expression.Convert(body, typeof(object));
return (Expression<Func<T, object>>) Expression.Lambda(convert, param);
}

Related

C# EF Core reflection order

When I have queryable such as
var query = dbSet.AsQueryable();
and I would like to dynamically set orderBy using reflection
var orderBy = "School.Name"
query = query.OrderBy("School.Name");
var data = await query.ToListAsync()
I do have extension to support order like this using reflection as following:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
Problem is to create lambda for inner object property such as School.Name. This solution works for Name attribute on Dbset object, but not on joined.
Goal is to modify ToLambda method to support joinned or inners properties.
using
query.OrderBy("School.Name");
// instead of
query.OrderBy(m => m.School.Name );
I have solved it as following:
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
if (propertyName.Contains('.'))
{
var parameter = Expression.Parameter(typeof(T));
var property = NestedProperty(parameter, propertyName.Split('.'));
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
else
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName.ToLower());
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
private static MemberExpression NestedProperty(Expression propertyHolder, params string[] propertyPath)
{
MemberExpression memberExpression = Expression.Property(propertyHolder, propertyPath[0]);
foreach (var member in propertyPath.Skip(1))
{
memberExpression = Expression.Property(memberExpression, member);
}
return memberExpression;
}

Dynamically create generic type of an Expression.Lambda

I would like to create lambda expressions, type:
Expression.Lambda <Func<T,TProperty>>
  but without knowing the TProperty, to save them in a collection.
public BuilderMapping<T> AutoMap()
{
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public);
foreach (var propertyInfo in props)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(param, propertyInfo.Name);
// propertyInfo.PropertyType == TProperty
// I have not TProperty
var exp = Expression.Lambda<Func<T, TProperty>>(body, param);
var prop = new PropertyDescriptor<T, TProperty>
{
Selector = exp
};
_descriptorColumn.Add(prop);
}
return this;
}
var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var exp = Expression.Lambda(delegateType, body, param);
PropertyDescriptor<T, TProperty> will need similar treatment, I think the shortest way would be this:
dynamic prop = Activator.CreateInstance(typeof(PropertyDescriptor<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType));
prop.Selector = (dynamic)exp;

Dynamic Create Func<T,TR> C#

I'm trying to create a dynamic lambda exp to filter some results on my list, but I can't figure out on how to create a dynamic func<,>
//Here I get the type of my object "Produto", but I can have other objects here...Like "Pedido" or another one.
Type type = typeof(Produto);
var param = Expression.Parameter(type, "arg");
var propName = Expression.Property(param, "Codigo");
var constP = Expression.Constant("123");
var nameStartsWith = Expression.Call(
propName,
typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
constP);
//Here I have to instantiate my Func<T, bool>
//I can't instantiate it with my variable "type" to use on my Where clausule.
//I don't know how to do this in another way, to create my lambda.
var WhereExp = Expression.Lambda<Func<type, bool>>(nameStartsWith, param);
return _uow.produto.GetAll().AsQueryable().Where(WhereExp).ToList();
You need to create a generic method. Something like this (untested):
public Expression<Func<T, bool>> DynamicWhere<T>(string pname, string value) where T : class
{
var param = Expression.Parameter(typeof(T), "arg");
var propName = Expression.Property(param, pname);
var constP = Expression.Constant(value);
var nameStartsWith = Expression.Call(
propName,
typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
constP);
return Expression.Lambda<Func<T, bool>>(nameStartsWith, param);
}
Then you can use it like this:
var WhereExp = DynamicWhere<Produto>("Codigo", "123");
return _uow.produto.GetAll().AsQueryable().Where(WhereExp).ToList();

Construct expression tree with chained properties?

I have a method that accepts Expression<Func<T, string>>, for example x => x.Name, and a term, and returns x => x.Name.Contains(term):
Given the model;
class X
{
public Y Y {get; set;}
}
class Y
{
public string Z {get; set;}
}
It works well for GenerateForMember<Y>(y => y.Z, "foobar"), but currently don't work for GenerateForMember<X>(x => x.Y.Z, "foobar"). It gives the exception
'Z' is not a member of 'UserQuery+X'
How to I update my method to work with chained properties?
Method is as follows:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T,string>> expression, string term)
{
var type = typeof(T);
var memberExpression = ((expression.Body.NodeType == ExpressionType.Convert)
? ((UnaryExpression)expression.Body).Operand
: expression.Body) as MemberExpression;
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
MemberExpression member = Expression.PropertyOrField(parameter, memberExpression.Member.Name);
var propertyInfo = memberExpression.Member as PropertyInfo;
var memberType = propertyInfo == null
? ((FieldInfo) memberExpression.Member).FieldType
: propertyInfo.PropertyType;
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(member, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
}
You are dissecting the original expression and later re-construct it again. This is not necessary. You can use expression.Body directly in order to create the method call. Like this, it should work with any lambda expression.
var type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
Try this:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T, string>> expression, string term)
{
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, expression.Parameters[0]);
}

How to get a dynamic/generic #Html.DisplayNameFor (o=>o.myProperty)

I tried to use DisplayNameFor without the direct use of the Model View, but using a variable of type Expression<Func<TModel, TValue>>
I thought that using the following function will solve it
//based on http://stackoverflow.com/questions/16208214/construct-lambdaexpression-for-nested-property-from-string
public static LambdaExpression createExpression(Type type, string propertyName)
{
var param = Expression.Parameter(type, "x");
Expression body = param;
foreach (var member in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
return Expression.Lambda(body, param);
}
but no...
when I use in my view like
# {
Model1 model1 = new Model1() { id = 1, code = "Code1", isActive = true, name = "Name1" };
System.Linq.Expressions.LambdaExpression exp = Utils.createExpression(model1.GetType(), "id");
}
#Html.DisplayNameFor(exp)
edit : it throws a CS0411 compilation error
Any ideas?
Try
public static LambdaExpression CreateExpression(Type type, string propertyName)
{
ParameterExpression parameter = Expression.Parameter(type, "x");
MemberExpression propertyAccess = Expression.Property(parameter, propertyName);
return Expression.Lambda(propertyAccess, parameter);
}

Categories

Resources