Reputation: 568
Im not used to working with expression funcs, but my problem is: I get the name of a property as a string, i then need to convert this to the appropriate expression.
Currently im doing something like this:
if (string.Equals(propertyString, "customerNo", StringComparison.InvariantCultureIgnoreCase))
{
return _repo.DoSomething(x=>x.CustomerNo);
}
if (string.Equals(propertyString, "customerName", StringComparison.InvariantCultureIgnoreCase))
{
return _repo.DoSomething(x => x.CustomerName);
}
With the repo function something like this:
public IEnumerable<ICustomer> DoSomething(Expression<Func<IObjectWithProperties, object>> express)
{
//Do stuff
}
What i would like to do, is use reflection like this:
var type = typeof(IObjectWithProperties);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if(string.Equals(property.Name,propertyString,StringComparison.InvariantCultureIgnoreCase))
return _repo.DoSomething(x => x.PROPERTY);
}
But I can't figure out a way to generate the expression func from the propertyinfo
EDIT: Mong Zhu's answer, i am able to create an expression using the property.
The reason i need this expression, is that im trying to dynamically set the orderby in an iqueryable.
public IEnumerable<Customer> List(Expression<Func<IObjectWithProperties, object>> sortColumn)
{
using (var context = _contextFactory.CreateReadOnly())
{
return context.Customers.OrderBy(sortColumn).ToList();
}
}
Using the answer like this:
public Customer Test(string sortColumn){
var type = typeof(IObjectWithProperties);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (string.Equals(property.Name, sortColumn, StringComparison.InvariantCultureIgnoreCase))
{
Expression<Func<IObjectWithProperties, object>> exp = u =>
(
u.GetType().InvokeMember(property.Name, BindingFlags.GetProperty, null, u, null)
);
return _customerRepository.List(exp);
}
}
}
I get an error:
System.InvalidOperationException : variable 'u' of type 'IObjectWithProperties' referenced from scope '', but it is not defined
EDIT:
The Customer return type inherits the IObjectWithProperties:
public class Customer: IObjectWithProperties
{
//properties
}
Upvotes: 1
Views: 538
Reputation: 23732
OK, after digging around I found a working solution for EF in this answer.
It has to be slightly modified
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
return Expression.Lambda<Func<T, object>>(property, parameter);
}
The call would look like this:
var type = typeof(IObjectWithProperties);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (string.Equals(property.Name, propertyString, StringComparison.InvariantCultureIgnoreCase))
{
var result = DoSomething(ToLambda<IObjectWithProperties>(property.Name));
}
}
I will assume that Customer
is an partial class which implements the interface IObjectWithProperties
and an extension to a existing database table. So your orderby method should look like this:
public IEnumerable<Customer> DoSomething(Expression<Func<IObjectWithProperties, object>> sortColumn)
{
using (var context = _contextFactory.CreateReadOnly())
{
return context.Customers.OrderBy(sortColumn).Cast<Customer>().ToList();
}
}
The important thing that you need to do here is to call Compile()
which will allow to be translated into an sql statement and send to the server for querying.
Since you are using an Interface as input parameter for the Func
the compiler
seems not be able to deduce that you partial class implements this interface.
So a further explicit Cast<Customer>()
call is necessary to establish the correct return type.
I hope this is understandable and helps you to solve also the second problem
This solution translates also the OrderBy
clause to SQL.
Disclaimer:
Unfortunately it works with string
properties, but up to now NOT with Int32
.
I am still trying to figure out why.
EDIT:
Meanwhile I found another solution in this answer by David Specht
This extension class can really be used as copy paste and it works on either type. Here is the important code that you need:
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}
/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
IComparer<object> comparer = null)
{
var param = Expression.Parameter(typeof(T), "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);
return comparer != null
? (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param),
Expression.Constant(comparer)
)
)
: (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param)
)
);
}
}
And your order method would look simply like this:
public IEnumerable<Customer> DoSomething(string propertyName)
{
using (var context = _contextFactory.CreateReadOnly())
{
return context.Customers.OrderBy(propertyName).ToList();
}
}
Upvotes: 1