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;
}
Related
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. ?
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"
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 1 year ago.
I have a list of objects. How can I order this list using property name?
string orderbyField = "Code";
List<object> l = FillList();
l = l.OrderBy(o => orderbyField);
Can I have an extension for this problem?
If you don't have to provide the property name as a string, it's pretty simple using dynamic:
List<object> l = FillList();
l = l.OrderBy(o => ((dynamic)o).Id);
If the property name has to be a string, then it gets more a bit complicated but can be done using reflection (although it is not very efficient):
l = l.OrderBy(o => o.GetType()
.GetProperty("Code")
.GetValue(o, null));
You should also think about adding some error handling, e.g. if the property doesn't exist.
Also, if all the elements in the list have same runtime type, then it would be much more efficient to compile a getter function using expression trees and reusing it (instead of directly using reflection).
public static Func<object, object> CreateGetter(Type runtimeType, string propertyName)
{
var propertyInfo = runtimeType.GetProperty(propertyName);
// create a parameter (object obj)
var obj = Expression.Parameter(typeof(object), "obj");
// cast obj to runtimeType
var objT = Expression.TypeAs(obj, runtimeType);
// property accessor
var property = Expression.Property(objT, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<object, object>)Expression.Lambda(convert, obj).Compile();
}
and use it like:
var codeGetter = CreateGetter(l[0].GetType(), "Code"); // using the 1st element as an example
l = l.OrderBy(o => codeGetter(o));
Order by property name without type reflection
public static class IQueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string
propertyName)
{
return (IQueryable<T>)OrderBy((IQueryable)source, propertyName);
}
public static IQueryable OrderBy(this IQueryable source, string propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(x,
Expression.PropertyOrField);
var selector = Expression.Lambda
(Expression.PropertyOrField(x, propertyName), x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderBy", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source,
string propertyName)
{
return (IQueryable<T>)OrderByDescending((IQueryable)source, propertyName);
}
public static IQueryable OrderByDescending(this IQueryable source, string
propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var selector = Expression.Lambda(Expression.PropertyOrField(x,
propertyName),x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
}
public static IEnumerable<TSource> ComplexOrderBy<TSource>(this IEnumerable<TSource> source, string orderString)
{
if (string.IsNullOrWhiteSpace(orderString))
{
return source;
}
IOrderedEnumerable<TSource> orderedQuery = null;
var sortingFields = orderString.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var propertyNames = typeof(TSource).GetProperties().Select(prop => prop.Name.ToLower()).ToImmutableHashSet();
for (var i = 0; i < sortingFields.Length; i++)
{
var sortingSet = sortingFields[i].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var sortBy = sortingSet[0].ToLower();
var isDescending = sortingSet.Length > 1 && sortingSet[1].Trim().ToLower() == "desc";
try
{
var propertySelector = sortBy.GetPropertySelector<TSource>();
orderedQuery = isDescending
?
(i == 0 ? source.OrderByDescending(propertySelector.Compile()) : orderedQuery.OrderByDescending(propertySelector.Compile()).ThenByDescending(propertySelector.Compile()))
:
(i == 0 ? source.OrderBy(propertySelector.Compile()) : orderedQuery.ThenBy(propertySelector.Compile()));
}
// Just ignoring illegal properties for simplicity
catch (ArgumentNullException) { }
catch (ArgumentException) { }
}
return orderedQuery ?? source;
}
public static Expression<Func<T, object>> GetPropertySelector<T>(this string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(T), "x");
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.Property(body, member);
}
return Expression.Lambda<Func<T, object>>(Expression.Convert(body, typeof(object)), parameterExpression);
}
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 1 year ago.
I have a list of objects. How can I order this list using property name?
string orderbyField = "Code";
List<object> l = FillList();
l = l.OrderBy(o => orderbyField);
Can I have an extension for this problem?
If you don't have to provide the property name as a string, it's pretty simple using dynamic:
List<object> l = FillList();
l = l.OrderBy(o => ((dynamic)o).Id);
If the property name has to be a string, then it gets more a bit complicated but can be done using reflection (although it is not very efficient):
l = l.OrderBy(o => o.GetType()
.GetProperty("Code")
.GetValue(o, null));
You should also think about adding some error handling, e.g. if the property doesn't exist.
Also, if all the elements in the list have same runtime type, then it would be much more efficient to compile a getter function using expression trees and reusing it (instead of directly using reflection).
public static Func<object, object> CreateGetter(Type runtimeType, string propertyName)
{
var propertyInfo = runtimeType.GetProperty(propertyName);
// create a parameter (object obj)
var obj = Expression.Parameter(typeof(object), "obj");
// cast obj to runtimeType
var objT = Expression.TypeAs(obj, runtimeType);
// property accessor
var property = Expression.Property(objT, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<object, object>)Expression.Lambda(convert, obj).Compile();
}
and use it like:
var codeGetter = CreateGetter(l[0].GetType(), "Code"); // using the 1st element as an example
l = l.OrderBy(o => codeGetter(o));
Order by property name without type reflection
public static class IQueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string
propertyName)
{
return (IQueryable<T>)OrderBy((IQueryable)source, propertyName);
}
public static IQueryable OrderBy(this IQueryable source, string propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(x,
Expression.PropertyOrField);
var selector = Expression.Lambda
(Expression.PropertyOrField(x, propertyName), x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderBy", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source,
string propertyName)
{
return (IQueryable<T>)OrderByDescending((IQueryable)source, propertyName);
}
public static IQueryable OrderByDescending(this IQueryable source, string
propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var selector = Expression.Lambda(Expression.PropertyOrField(x,
propertyName),x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
}
public static IEnumerable<TSource> ComplexOrderBy<TSource>(this IEnumerable<TSource> source, string orderString)
{
if (string.IsNullOrWhiteSpace(orderString))
{
return source;
}
IOrderedEnumerable<TSource> orderedQuery = null;
var sortingFields = orderString.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var propertyNames = typeof(TSource).GetProperties().Select(prop => prop.Name.ToLower()).ToImmutableHashSet();
for (var i = 0; i < sortingFields.Length; i++)
{
var sortingSet = sortingFields[i].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var sortBy = sortingSet[0].ToLower();
var isDescending = sortingSet.Length > 1 && sortingSet[1].Trim().ToLower() == "desc";
try
{
var propertySelector = sortBy.GetPropertySelector<TSource>();
orderedQuery = isDescending
?
(i == 0 ? source.OrderByDescending(propertySelector.Compile()) : orderedQuery.OrderByDescending(propertySelector.Compile()).ThenByDescending(propertySelector.Compile()))
:
(i == 0 ? source.OrderBy(propertySelector.Compile()) : orderedQuery.ThenBy(propertySelector.Compile()));
}
// Just ignoring illegal properties for simplicity
catch (ArgumentNullException) { }
catch (ArgumentException) { }
}
return orderedQuery ?? source;
}
public static Expression<Func<T, object>> GetPropertySelector<T>(this string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(T), "x");
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.Property(body, member);
}
return Expression.Lambda<Func<T, object>>(Expression.Convert(body, typeof(object)), parameterExpression);
}
I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}