I saw already some of the similar questions, but cannot find an anwser how to solve this problem.
I want to have a possibility to order collection using string as property name.
Model:
public sealed class User : IdentityUser
{
#region Constructors
#endregion
#region Properties
[Required]
[Display(Name = "First name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last name")]
public string LastName { get; set; }
[ForeignKey("Superior")]
public string SuperiorId { get; set; }
public User Superior{ get; set; }
[NotMapped]
public string FullName => this.LastName + " " + this.FirstName;
#endregion
}
And I want to make a call like this:
DbSet.Order("SuperiorUser.FullName", Asc/Desc)...
My below functions work for a Order("FullName", Asc/Desc), but dont when I want to go deeper.
public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source, string propertyName,
ListSortDirection direction = ListSortDirection.Ascending)
{
return ListSortDirection.Ascending == direction
? source.OrderBy(ToLambda<T>(propertyName))
: source.OrderByDescending(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);
return Expression.Lambda<Func<T, object>>(property, parameter);
}
So I made it look a little bit like this
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var propertyNames = propertyName.Split('.');
var type = typeof(T)
ParameterExpression parameter;
MemberExpression property;
for (var propName in propertyNames)
{
parameter = Expression.Parameter(type);
property = Expression.Property(parameter, propName);
type = property.Type;
}
return Expression.Lambda<Func<T, object>>(property, parameter);
}
But this unfortunately returns error The parameter '' was not bound in the specified LINQ to Entities query expression. What am I missing?
You need to nest the Property access methods in the loop and then apply the parameter to the lambda.
private static Expression<Func<T, object>> ToLambda<T>(string propertyName) {
var propertyNames = propertyName.Split('.');
var parameter = Expression.Parameter(typeof(T));
Expression body = parameter;
foreach (var propName in propertyNames)
body = Expression.Property(body, propName);
return Expression.Lambda<Func<T, object>>(body, parameter);
}
I once wrote a extention method for IQueryables to sort by a property defined by string:
public static IOrderedQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
else
{
if (propertyName.EndsWith(" ASC", StringComparison.OrdinalIgnoreCase))
propertyName = propertyName.Replace(" ASC", "");
// DataSource control passes the sort parameter with a direction
// if the direction is descending
int descIndex = propertyName.IndexOf(" DESC", StringComparison.OrdinalIgnoreCase);
if (descIndex >= 0)
{
propertyName = propertyName.Substring(0, descIndex).Trim();
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
MemberExpression property = Expression.Property(parameter, propertyName);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = (descIndex < 0) ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression) as IOrderedQueryable<T>;
}
}
You may call then LIST.AsQueryable().SortBy("Surname").
Related
I'd want to refactor this code to get rid of relying on TModel, TValue
public static string DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
// null checks
DescriptionAttribute descriptionAttribute = null;
if (expression.Body is MemberExpression memberExpression)
{
descriptionAttribute = memberExpression.Member
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.SingleOrDefault();
}
return descriptionAttribute?.Description ?? string.Empty;
}
which has invocation like that:
#Html.DescriptionFor(x => x.MyModel.MyOtherModel.Property)
to something like that
public static string DescriptionFor2<T>(Expression<Func<T>> expression)
{
if (expression == null)
throw new ArgumentNullException(nameof(expression));
if (!(expression.Body is MemberExpression memberExpression))
{
return string.Empty;
}
foreach (var property in typeof(T).GetProperties())
{
if (property == ((expression.Body as MemberExpression).Member as PropertyInfo))
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
return attr?.Description ?? string.Empty;
}
}
return string.Empty;
}
with invocation like that:
#MyHtml.DescriptionFor2<MyOtherModel>(x => x.MyProperty);
But here's error:
Delegate 'Func' does not take 1 arguments
You can do this:
void Main()
{
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.StringMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.BooleanMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.IntegerMember));
}
public class Test
{
[Description("This is a string member")]
public string StringMember { get; set; }
[Description("This is a boolean member")]
public bool BooleanMember { get; set; }
[Description("This is a integer member")]
public int IntegerMember { get; set; }
}
public static class MyHtml
{
public static string DescriptionFor2<T>(Expression<Func<T, dynamic>> expression)
{
var result = string.Empty;
var member = GetMemberExpression(expression)?.Member?.Name;
if (member != null)
{
var property = typeof(T).GetProperty(member);
if (property != null)
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
result = attr?.Description ?? string.Empty;
}
}
return result;
}
private static MemberExpression GetMemberExpression<T>(Expression<Func<T, dynamic>> expression)
{
var member = expression.Body as MemberExpression;
var unary = expression.Body as UnaryExpression;
return member ?? (unary != null ? unary.Operand as MemberExpression : null);
}
}
public class DescriptionAttribute : Attribute
{
public string Description { get; set; }
public DescriptionAttribute(string description)
{
Description = description;
}
}
It involves getting the body of the expression as a member and using its name to resolve the property on the object. The dynamic data type is used to get rid of the second type parameter, however you can also define it explicitly for better compile-time error catching:
public static string DescriptionFor2<T, U>(Expression<Func<T, U>> expression)
and calling it:
MyHtml.DescriptionFor2<Test, string>((t) => t.StringMember);
MyHtml.DescriptionFor2<Test, bool>((t) => t.BooleanMember);
MyHtml.DescriptionFor2<Test, int>((t) => t.IntegerMember);
EDIT: edited answer because initial solution did not work for value types, see Expression for Type members results in different Expressions (MemberExpression, UnaryExpression)
Below are my classes:
public class A
{
public int i { get; set; }
}
public class B
{
public A a { get; set; }
public int j { get; set; }
}
If I want to use expression to express b.j>3, I can do it like this:
ParameterExpression parameter = Expression.Parameter(typeof(B), "b");
MemberExpression member = Expression.PropertyOrField(parameter, "j");
ConstantExpression constant = Expression.Constant(3);
return Expression.Lambda<Func<B, bool>>(Expression.GreaterThan(member, constant), parameter);
Now, how can I build an expression for b.a.i>3?
Just create another MemberExpression based on the previous one:
ParameterExpression parameter = Expression.Parameter(typeof(B), "b");
MemberExpression memberA = Expression.PropertyOrField(parameter, "a");
// Use the previous MemberExpression here:
MemberExpression memberI = Expression.PropertyOrField(memberA, "i");
ConstantExpression constant = Expression.Constant(3);
return Expression.Lambda<Func<B, bool>>(Expression.GreaterThan(memberI, constant), parameter);
I am trying to modify MemberBinding expression using Expression visitor. But I am getting an error when I try to compile the modified expression which says:
'variable 'source' of type 'EFTest.Views.TestView' referenced from scope '', but it is not defined'
I would also like to mention that i do not get any error if I return MemberBinding as it is in VisitMemberBinding function. I have also made sure that the modified Expression is exactly same as the orignal one.
Final purpose of this work will be to block a specific column in all linq queries in EntityFramework Core. Please let me know if you have suggestions regarding that.
Sample codes are below
class Expre
{
static void Main(string[] args)
{
DataContext context = new DataContext();
var rs = context.Query<TestView>().Select(m => new MyModel()
{
Id = m.DocumentId,
});
Visitor visitor = new Visitor();
var ss = visitor.Modify(rs.Expression);
var CompiledLambdaExpression = Expression.Lambda(ss).Compile();
EntityQueryable<MyModel> queryAble = (EntityQueryable<MyModel>)CompiledLambdaExpression.DynamicInvoke();
var rss = Enumerable.ToList(queryAble);
}
}
public class Visitor : ExpressionVisitor
{
public MemberBinding MemberBinding;
public Expression Modify(Expression expression)
{
var r = Visit(expression);
return r;
}
protected override MemberBinding VisitMemberBinding(MemberBinding node)
{
Type targetType = typeof(MyModel);
Type sourceType = typeof(TestView);
PropertyInfo SourceProperty = sourceType.GetProperty("DocumentId");
PropertyInfo TargetProperty = targetType.GetProperty("Id");
ParameterExpression parameterExpression = Expression.Parameter(sourceType, "m");
MemberExpression propertyExpression = Expression.Property(parameterExpression, SourceProperty);
MemberBinding memberBinding = Expression.Bind(TargetProperty, propertyExpression);
return base.VisitMemberBinding(memberBinding);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(node);
}
}
public class MyModel
{
public Int32 Id { get; set; }
public string Name { get; set; }
}
public class TestView
{
public Int32 Code { get; set; }
public string LanguageCode { get; set; }
public Int32 DocumentId { get; set; }
}
I'm creating a generic Where filter that works great so far (only interested in Contains filters):
private static MethodInfo contains = typeof(string).GetMethod("Contains");
private static Expression<Func<T, bool>> GetFilter<T>(string propertyName, string value)
{
var item = Expression.Parameter(typeof(T), "item");
var member = Expression.Property(item, propertyName);
var constant = Expression.Constant(value);
var body = Expression.Call(member, contains, constant);
return Expression.Lambda<Func<T, bool>>(body, item);
}
Is there any way I could extend this so that I could search by a navigation property? I'm very new to using Expression so I'm not sure what to try.
An example would be:
public class A
{
public int BId { get; set; }
public B B { get; set; }
}
public class B
{
public string Name { get; set; }
}
dbContext.As
.Where(GetFilter<A>("B.Name", "Hello World"))
.ToList();
But this fails on Expression.Property("B.Name") with:
Instance property B.Name is not defined for type A
You need to create each member access sequentially for each property in the property path :
private static Expression<Func<T, bool>> GetFilter<T>(string propertyName, string value)
{
var item = Expression.Parameter(typeof(T), "item");
Expression member = item;
foreach (var prop in propertyName.Split('.'))
{
member = Expression.Property(member, prop);
}
var constant = Expression.Constant(value);
var body = Expression.Call(member, contains, constant);
return Expression.Lambda<Func<T, bool>>(body, item);
}
We have an entity of type T1 which has a member of type T.
something like this :
public class T1
{
public T Member{get;set;}
}
User can use our UI to give us a filter over T and we have translate it to an expression of a function that gets a T and returns bool (Expression<Func<T,bool>>)
I would like to know is it possible to convert this to an expression of a function that gets T1 and returns bool.
Actually I'd like to convert this :
(t=>t.Member1==someValue && t.Member2==someOtherValue);
to this :
(t1=>t1.Member.Member1==someValue && t1.Member.Member2==someOtherValue);
You can do it with a few way.
First and simplest: use Expression.Invoke
Expression<Func<T, bool>> exprT = t.Member1==someValue && t.Member2==someOtherValue
ParameterExpression p = Expression.Parameter(typeof(T1));
var expr = Expression.Invoke(expr, Expression.PropertyOrField(p, "Member"));
Expression<Func<T1, bool>> exprT1 = Expression.Lambda<Func<T1, bool>>(expr, p);
but in this case you get not
t1 => (t=>(t.Member1==someValue && t.Member2==someOtherValue))(t1.Member),
instead of
(t1=>t1.Member.Member1==someValue && t1.Member.Member2==someOtherValue);
For replacing you can use ExpressionVisitor class like
class V : ExpressionVisitor
{
public ParameterExpression Parameter { get; private set; }
Expression m;
public V(Type parameterType, string member)
{
Parameter = Expression.Parameter(parameterType);
this.m = Expression.PropertyOrField(Parameter, member);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == m.Type)
{
return m;
}
return base.VisitParameter(node);
}
}
and use it
var v = new V(typeof(T1), "Member");
var exprT1 = Expression.Lambda<Func<T1, bool>>(v.Visit(exprT.Body), v.Parameter);
Given
public class MyClass
{
public MyInner Member { get; set; }
}
public class MyInner
{
public string Member1 { get; set; }
public string Member2 { get; set; }
}
plus
public static Expression<Func<TOuter, bool>> Replace<TOuter, TInner>(Expression<Func<TInner, bool>> exp, Expression<Func<TOuter, TInner>> outerToInner)
{
var body2 = new ExpressionReplacer { From = exp.Parameters[0], To = outerToInner.Body }.Visit(exp.Body);
var lambda2 = Expression.Lambda<Func<TOuter, bool>>(body2, outerToInner.Parameters);
return lambda2;
}
and
public class ExpressionReplacer : ExpressionVisitor
{
public Expression From;
public Expression To;
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == From)
{
return base.Visit(To);
}
return base.VisitParameter(node);
}
}
you can
// The initial "data"
string someValue = "Foo";
string someOtherValue = "Bar";
Expression<Func<MyInner, bool>> exp = t => t.Member1 == someValue && t.Member2 == someOtherValue;
Expression<Func<MyClass, MyInner>> outerToInner = u => u.Member;
// The "new" expression
Expression<Func<MyClass, bool>> result = Replace(exp, outerToInner);
The ExpressionReplacer class replaces a parameter of an expression with another expression, while the Replace method uses the ExpressionReplacer and then rebuilds a new expression.