Property Name to Lambda Expression C# - c#

How can I convert a property name to Lambda expression in C#?
Like this: string prop = "Name"; to (p => p.Name)
public class Person{
public string Name{ get; set; }
}
Thanks!

Using expression trees you can generate the lambda expression.
using System.Linq.Expressions;
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(arg, propertyName);
//return the property as object
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
for Person you can call it like:
var exp = GetPropertySelector<Person>("Name");//exp: x=>x.Name

A lambda is just an anonymous function. You can store lambdas in delegates just like regular methods. I suggest you try making "Name" a property.
public string Name { get { return p.Name; } }
If you really want a lambda, use a delegate type such as Func.
public Func<string> Name = () => p.Name;

Related

How to retrieve a property of an object from a string?

In order to use it as a selector in a Grouping Clause at runtime, I want basically retrieve a property from an object T by passing its name. Example with this class:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
}
I want to use my method like this:
private void MyMethod(string propertyName)
{
var lambda = GetLambdaExpression(propertyName);
// The problem is to specify the Type at compile time...
var selector = GetLambdaSelector<DateTime>(lambda); // Seems mandatory to convert LambdaExpression to Expression<Func<Person, T>> to use in a grouping clause
var result = emps.GroupBy(
selector.Compile(),
p => p.Birthday,
(key, g) => new { PersonName = key, = g.ToList() }).AsQueryable();
}
Here are methods statements :
LambdaExpression GetLambdaExpression(string propertyName)
{
var type = typeof(Person);
var propertyInfo = type.GetProperty(propertyName);
var propertyType = propertyInfo.PropertyType;
var entityType = propertyInfo.DeclaringType;
var parameter = Expression.Parameter(entityType, "entity");
var property = Expression.Property(parameter, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, parameter);
return lambda;
}
Expression<Func<Person, T>> GetLambdaSelector<T>(LambdaExpression expression)
{
return (Expression<Func<Person, T>>)expression;
}
I'm stuck because I want to resolve the type (DateTimein the example) at runtime and not at compile time.
So how to call this method with type resolve at runtime:
GetLambdaSelector<?>(lambda); ?
Or maybe can I change the signature of LambdaExpression GetLambdaExpression(string propertyName) to Expression<Func<Person, T>> with reflection but I did not manage it for the moment. ?

How can I convert a Parameter Expression into a function?

If I have a dynamically created ParameterExpression:
class Product
{
public string Name { get; set; }
}
var propertyName = "Name";
var propertyType = typeof(Product).GetProperty(propertyName).PropertyType;
var parameterExpression = Expression.Parameter(propertyType , propertyName);
How can I covert it into a Func<Product, TPropertyType>?
I specifically want to pass this into the Where or OrderBy linq methods used by entity framework.
I'm also open to other suggestions not using Expressions, but I highly doubt it's possible.
Edit 1: Removed the where use case as Where and OrderBy will have different implementations Removed in an attempt to narrow the scope of the question.
Here is an example with generating expressions for OrderBy and Where. As Johnathon Sullinger said in comments, you must know type of property you ordering by at compile time, because it is mentioned in signature of OrderBY. However you don't have to know it for Where:
class Product
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var products = new List<Product> {
new Product { Name = "ZZZ"},
new Product { Name = "AAA"}
};
var propertyName = "Name";
var ordered = products.AsQueryable().OrderBy(GetOrderExpression<string>(propertyName));
Console.WriteLine(ordered.ElementAt(0).Name);
Console.WriteLine(ordered.ElementAt(1).Name);
var filtered = products.AsQueryable().Where(GetWhereExpression(propertyName, "AAA"));
Console.WriteLine(filtered.Count());
Console.WriteLine(filtered.ElementAt(0).Name);
Console.ReadKey();
}
static Expression<Func<Product, TKey>> GetOrderExpression<TKey>(string propertyName)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var lambda = Expression.Lambda<Func<Product, TKey>>(prop, "p", new[] { prm });
return lambda;
}
static Expression<Func<Product, bool>> GetWhereExpression(string propertyName, object value)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var equal = Expression.Equal(prop, Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(equal, "p", new[] { prm });
return lambda;
}
Hope it helps.

cast left side of expression predicate into string

i am having a generic method which return an expression predicate to filter data from list.
MemberExpression member = Expression.Property(param, filter.ColumnName);
ConstantExpression constant = Expression.Constant(filter.TextToBeFiltered);
switch (filter.FilterOperation)
{
case FilterEnum.Equals:
return Expression.Equal(member, constant);
}
var res = List.Where(reqExpression).ToList();
Problem is the properties in list some of are string,int,guid etc so i want to cast left side of expression into string because i need to compare all properties with string only like a=> a.Id.tostring() == inputstringso how to perform it in my code.
Take a look at this full example:
class Program
{
class MyType
{
public int Column { get; set; }
};
public static string AsString(object obj)
{
return obj?.ToString();
}
static void Main(string[] args)
{
var param = Expression.Parameter(typeof(MyType));
//your member
MemberExpression member = Expression.Property(param, "Column");
var asString = typeof(Program).GetMethod("AsString");
var stringMember = Expression.Call(asString, Expression.Convert(member, typeof(object)));
//your value
ConstantExpression constant = Expression.Constant("23");
//your switch
var expression = Expression.Equal(stringMember, constant);
var lambda = Expression.Lambda(expression, param);
var list = new List<MyType>
{
new MyType{Column = 23},
new MyType{Column= 24}
};
var res = list.Where((Func<MyType,bool>)lambda.Compile()).ToList();
}
}
You can also use ToString method (beware nulls!) or Convert.ChangeType. Own AsString method is good for custom types and... for debugging.
Think as building code:
p => AsString((object)p.Column) == "23"

LambdaExpression to Expression via Extensions Method

I looked at the other SO versions of this question but it seems the casting out of a method works for others. I am not sure what I am doing wrong here. I am new to the Expression Building part of Linq.
My extensions method is as follows:
void Main()
{
var people = LoadData().AsQueryable();
var expression = people.PropertySelector<Person>("LastName");
expression.Should().BeOfType(typeof(Expression<Func<Person, object>>));
var result = people.OrderBy(expression);
}
public static class Extensions
{
public static Expression<Func<T, object>> PropertySelector<T>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, object>>)lambda;
}
}
#region
private Random rand = new Random();
// Define other methods and classes here
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public IEnumerable<Person> LoadData()
{
IList<Person> people = new List<Person>();
for (var i = 0; i < 15; i++)
{
people.Add(new Person
{
FirstName = $"FirstName {i}",
LastName = $"LastName {i}",
Age = rand.Next(1, 100)
});
}
return people;
}
#endregion
I get an exception on the return during the cast. At this point T is type Person and object is a string. My lambda.GetType() is reporting that it's of type Expression<Func<Person, string>> The exception is:
Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.String]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.Object]]'.
What about my cast is incorrect? Thanks.
EDIT:
I updated with my full code that I am playing with in LinqPad. I am really just trying to figure out if there is an easy way to generate a lambda expression by passing in the property name. I was doing something like this before but I was just doing a switch on property name then using lambda syntax to create the OrderBy query dynamically.
This is strictly me trying to learn how Expression static methods can be used to achieve the same result as the example below. I am trying to mimic the below via extension method. But it doesn't have to be that way. It was just easiest to try while dinking around in LinqPad.
Expression<Func<Loan, object>> sortExpression;
switch (propertyFilter)
{
case "Age":
sortExpression = (l => l.Age);
break;
case "LastName":
sortExpression = (l => l.LastName);
break;
default:
sortExpression = (l => l.FirstName);
break;
}
var sortedLoans = loans.AsQueryable().OrderBy(sortExpression);
sortedLoans.Dump("Filtered Property Result");
Your code is creating a Func<UserQuery, String> because you're geting it's intrinsic type with
var propertyInfo = properties.Single(p => p.Name == propertyName);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
If you want to return a Func<T, object> then create a Func<T, object>, not a Func<T, (reflected property type)>, else the better solution is to use a Func<TOut, TIn> and create a totally generic function.
To keep current method signature you could do this:
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object));
var typeAs = Expression.TypeAs(property, typeof(object));
var lambda = Expression.Lambda(funcType, typeAs, alias);
and better way is to change your method to
public static Expression<Func<T, Tout>> PropertySelector<T, Tout>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, Tout>>)lambda;
}
and call it with
var expression = people.PropertySelector<Person, string>("LastName");
I got the result I wanted. After comments from #Gusman, #IvanStoev and #PetSerAl I got it to function. I can move on with my exploring and learning again. Thank you very much. Final result was to template the Propertytype.
public static Expression<Func<T, TPropertyType>> PropertySelector<T, TPropertyType>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(TPropertyType));
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, TPropertyType>>)lambda;
}

TextareaFor on nullable types with reflection

public Expression GetValue<T>(T source, PropertyInfo property)
{
ParameterExpression fieldName = Expression.Parameter(pi.PropertyType, pi.Name);
Expression fieldExpr = Expression.PropertyOrField(Expression.Constant(source), pi.Name);
var exp = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), fieldExpr.Type),
fieldExpr,
fieldName);
return exp;
}
Here is my calling function
public MvcHtmlString RenderModel(T model)
{
foreach (var prop in typeof(T).GetProperties())
{
var x = InputExtensions.TextBoxFor(h, (dynamic) GetValue<T>(model, prop));
}
return new MvcHtmlString(string.Empty);
}
Here is an example class:
public class Test
{
public int? ID { get; set; }
public string Name { get; set; }
public DateTime? Date{ get; set; }
}
I adopted GetValue from part of:
Member Expression cannot convert to object from nullable decimal
which seems to work quite well until you reach properties that are not of type object I get this error:
ParameterExpression of type 'System.Nullable`1[System.Int32]' cannot be used for delegate parameter of type 'Test'
I'm not really sure where I've gone wrong here, and what that error even means
If I understand correctly, you need property access lambda. Here is the correct version of GetValue<T> method:
public Expression GetValue<T>(T source, PropertyInfo pi)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
body = Expression.PropertyOrField(body, pi.Name);
return Expression.Lambda(body, param);
}
If you want to make an Expression by providing a delegate (that in your question it is Func<T,..> ) then you have to provide the delegate type, body expression and parameter expressions too:
public Expression GetValue<T>(T source, PropertyInfo property)
{
return Expression.Lambda(
delegateType: typeof(Func<,>).MakeGenericType(typeof(T), pi.PropertyType),
body: Expression.PropertyOrField(Expression.Parameter(typeof(T), "x"), pi.Name),
parameters: Expression.Parameter(typeof(T), "x"));
}

Categories