C# EF Core reflection order - c#

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;
}

Related

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;

c# Expression Lambda to Func<T> from string

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);
}

An Entity Framework expression to find entities that don't match some property

I need a custom expression which works in Entity Framework. The method should have this signature:
var ids = new List<int> { 1, 2, 3 };
Context.FooEntities.WithoutId(e => e.Id, ids);
That should give me all Foo entities which do not have Id properties that match those in the list.
My attempt is based on an existing example here.
public static IQueryable<T> WithoutId<T>(
this IQueryable<T> entities,
Expression<Func<T, int>> propertySelector,
ICollection<int> ids) {
var property = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T));
var expression = Expression.Lambda<Func<T, bool>>(
Expression.Not(
Expression.Call(
Expression.Constant(ids),
typeof(ICollection<int>).GetMethod("Contains"),
Expression.Property(parameter, property))),
parameter);
return entities.Where(expression);
}
The problem is when ids is empty then it returns all entities. It should return no entities.
How about this? (just as idea, not completed code)
IEnumerable<Entity> Get()
{
var ids = new[] { 1, 2, 3 };
if (ids.Length == 0) return Enumerable.Empty<Entity>();
return MyContext.MyEntities.Where(x=>ids.Contains(x.Id)).ToArray();
}
In case the list of ids is empty, just return an empty collection:
if (ids.Count() != 0)
{
var property = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T));
var expression = Expression.Lambda<Func<T, bool>>(
Expression.Not(
Expression.Call(
Expression.Constant(ids),
typeof(ICollection<int>).GetMethod("Contains"),
Expression.Property(parameter, property))),
parameter);
return entities.Where(expression);
}
return new List<T>().AsQueryable()//Or Enumerable.Empty<T>().AsQueryable();
You can try as shown below.
public static IQueryable<T> WithoutId<T>(this IQueryable<T> entities,Expression<Func<T, int>> propertySelector,ICollection<int> ids) {
if (ids.Any())
{
var property = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T));
var expression = Expression.Lambda<Func<T, bool>>(
Expression.Not(
Expression.Call(
Expression.Constant(ids),
typeof(ICollection<int>).GetMethod("Contains"),
Expression.Property(parameter, property))),parameter);
return entities.Where(expression);
}
else{
return Enumerable.Empty<T>().AsQueryable();
}
}
My question can be solved without a complicated expression, as indicated in the above answers (using a simple "where+contains", which is supported by EF).
But, the formal way could be this, which seems to work for me (even though it's overkill):
public static IQueryable<T> WithoutId<T>(
this IQueryable<T> entities,
Expression<Func<T, int>> propertySelector,
ICollection<int> ids) {
if (!ids.Any()) { // here is the trick
/*
expression = Expression.Lambda<Func<TEntity, bool>>(
Expression.Constant(false),
parameter);
*/
return Enumerable.Empty<T>().AsQueryable()
}
var property = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T));
var expression = Expression.Lambda<Func<T, bool>>(
Expression.Not(
Expression.Call(
Expression.Constant(ids),
typeof(ICollection<int>).GetMethod("Contains"),
Expression.Property(parameter, property))),
parameter);
return entities.Where(expression);
}

Convert Expression<Func<T, TProperty>> to Expression<Func<object, object>> and vice versa

Is there a way to convert the property selector Expression<Func<T, TProperty>> to Expression<Func<object, object>> and vice versa? I already know how to convert to Expression<Func<T, object>> using...
Expression<Func<T, TProperty>> oldExp;
Expression.Lambda<Func<T, object>>(Expression.Convert(oldExp.Body, typeof(object)), oldExp.Parameters);
... but I need to effectively cast both, the argument and the result of the function, not just e.g replace them with a ExpressionVisitor because they need to be casted back later.
You were correct that you need to use an ExpressionVisitor and ExpressionConvert.
Here are the two methods that you asked for (as well as some support methods):
public Expression<Func<object, object>> ConvertToObject<TParm, TReturn>(Expression<Func<TParm, TReturn>> input)
{
var parm = Expression.Parameter(typeof(object));
var castParm = Expression.Convert(parm, typeof(TParm));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(object));
return Expression.Lambda<Func<object, object>>(body, parm);
}
public Expression<Func<TParm, TReturn>> ConvertBack<TParm, TReturn>(Expression<Func<object, object>> input)
{
var parm = Expression.Parameter(typeof(TParm));
var castParm = Expression.Convert(parm, typeof(object));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(TReturn));
return Expression.Lambda<Func<TParm, TReturn>>(body, parm);
}
Expression ReplaceExpression(Expression body, Expression source, Expression dest)
{
var replacer = new ExpressionReplacer(source, dest);
return replacer.Visit(body);
}
public class ExpressionReplacer : ExpressionVisitor
{
Expression _source;
Expression _dest;
public ExpressionReplacer(Expression source, Expression dest)
{
_source = source;
_dest = dest;
}
public override Expression Visit(Expression node)
{
if (node == _source)
return _dest;
return base.Visit(node);
}
}
Sample usage:
Expression<Func<Customer, string>> expression = c => c.Name;
var convertedExpression = ConvertToObject<Customer, string>(expression);
var backExpression = ConvertBack<Customer, string>(convertedExpression);
Of course we can replace the original two methods with one method with more type parameters:
public Expression<Func<TTargetParm, TTargetReturn>> ConvertGeneric<TParm, TReturn, TTargetParm, TTargetReturn>(Expression<Func<TParm, TReturn>> input)
{
var parm = Expression.Parameter(typeof(TTargetParm));
var castParm = Expression.Convert(parm, typeof(TParm));
var body = ReplaceExpression(input.Body, input.Parameters[0], castParm);
body = Expression.Convert(body, typeof(TTargetReturn));
return Expression.Lambda<Func<TTargetParm, TTargetReturn>>(body, parm);
}
Sample usage:
Expression<Func<Customer, string>> expression = c => c.Name;
var convertedExpression = ConvertGeneric<Customer, string, object, object>(expression);
var backExpression = ConvertGeneric<object, object, Customer, string>(convertedExpression);

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