How to build memberexpression of memberexpression? - c#

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

Related

How to modify MemberBinding expression using Expression Visitor

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

EntityFramework LINQ Order using string and nested reflection

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").

Dynamic Where filter through navigation Property

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

Convert an Expression<Func<T,bool>> to an Expression<Func<T1,bool>> so that T is a member of T1

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.

Expression tree for a member access of depth > 1

public class Job
{
public string Name { get; set; }
public int Salary { get; set; }
}
public class Employee
{
public string Name { get; set; }
public Job Job { get; set; }
}
If I want to create an expression tree of a member access to Employee.Name this is what I do:
var param = Expression.Parameter(type, "x");
var memberAccess = Expression.PropertyOrField(param, memberName);
return Expression.Lambda<Func<TModel, TMember>>(memberAccess, param);
What is the equivalent to this for a member access to Employee.Job.Salary ?
You need:
var jobProperty = Expression.PropertyOrField(param, "Job");
var salaryProperty = Expression.PropertyOrField(jobProperty, "Salary");
Basically you're taking the Salary property from the result of evaluating x.Job.
If you need to do this in a programmatic way, you'll need something like:
Expression expression = Expression.Parameter(type, "x");
foreach (var property in properties.Split('.'))
{
expression = Expression.PropertyOrField(expression, property);
}
The best way will be create Extension as here:
public static class ExpressionExtensions
{
/// <summary>
/// create expression by property name
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="propertyName">
/// <example>Urer.Role.Name</example>
/// </param>
/// <returns></returns>
public static Expression<Func<TModel, dynamic>> CreateExpression<TModel>(this string propertyName) {
Type currentType = typeof (TModel);
ParameterExpression parameter = Expression.Parameter(currentType, "x");
Expression expression = parameter;
int i = 0;
List<string> propertyChain = propertyName.Split('.').ToList();
do {
System.Reflection.PropertyInfo propertyInfo = currentType.GetProperty(propertyChain[i]);
currentType = propertyInfo.PropertyType;
i++;
if (propertyChain.Count == i)
{
currentType = typeof (object);
}
expression = Expression.Convert(Expression.PropertyOrField(expression, propertyInfo.Name), currentType);
} while (propertyChain.Count > i);
return Expression.Lambda<Func<TModel, dynamic>>(expression, parameter);
}
}
You cannot Convert() to typeof(object) everytime, because System.Object doesn't have property, that your type has (like Name or Salary in you example).

Categories

Resources