Reputation: 3328
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");
Upvotes: 0
Views: 1331
Reputation: 3328
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;
}
Upvotes: 0
Reputation: 2185
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");
Upvotes: 1
Reputation: 15151
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.
Upvotes: 1