Expression tree for a member access of depth > 1 - c#

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

Related

How to build memberexpression of memberexpression?

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

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

Rewriting Linq Select to a new subpath

I am currently trying to dynamically build linq queries. I want to be able to reuse Linq expressions in other Linq expressions.
For example:
public class Dummy
{
public string Test { get; set; }
public Sub Sub { get; set; }
}
public class Sub
{
public static Expression<Func<Sub, string>> Converter
{
get
{
return x => x.Id + ": " + x.Text;
}
}
public int Id { get; set; }
public string Text { get; set; }
}
When writing the converter for the Dummy class, the converter should be able to reuse the Sub.Converter. For thi spurpose I have written a DynamicSelect<> extension method:
var result = Query
.Where(x=>x.Sub != null)
.DynamicSelect(x => new Result())
.Select(x => x.SubText, x => x.Sub,Sub.Converter)
.Apply()
.ToList();
DynamicSelect creates a new SelectionBuilder,
the select part takes as first input the targetproperty (Result.SubText),
as second property the input property which we want to convert and as third input the converter for the Sub Property.
The .Apply call then returns the builtup expression tree.
I managed to get it working for a simpler usecase (without the subpath):
var result = Query.DynamicSelect(x => new Result())
.Select(x => x.ResolvedTest, x => x.Inner == null ? x.Test : x.Inner.Test)
.Select(x => x.SubText, x => x.Sub == null ? null : x.Sub.Text)
.Apply()
.ToList();
But how do I rebase an expression to another subpath?
Code so far:
public static class SelectBuilder
{
public static LinqDynamicConverter<TInput,TOutput> DynamicSelect<TInput,TOutput>(
this IQueryable<TInput> data,
Expression<Func<TInput, TOutput>> initialConverter) where TOutput : new()
{
return new LinqDynamicConverter<TInput,TOutput>(data, initialConverter);
}
}
public class LinqDynamicConverter<TInput,TOutput> where TOutput: new()
{
#region inner classes
private class MemberAssignmentVisitor : ExpressionVisitor
{
private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }
public MemberAssignmentVisitor(IDictionary<MemberInfo, MemberAssignment> singlePropertyToBinding)
{
SinglePropertyToBinding = singlePropertyToBinding;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
SinglePropertyToBinding[node.Member] = node;
return base.VisitMemberAssignment(node);
}
}
private class MemberInfoVisitor : ExpressionVisitor
{
internal MemberInfo SingleProperty { get; private set; }
public MemberInfoVisitor()
{
}
protected override Expression VisitMember(MemberExpression node)
{
SingleProperty = node.Member;
return base.VisitMember(node);
}
}
#endregion
#region properties
private IQueryable<TInput> Data { get;set; }
private Expression<Func<TInput, TOutput>> InitialConverter { get;set;}
private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }
#endregion
#region constructor
internal LinqDynamicConverter(IQueryable<TInput> data,
Expression<Func<TInput, TOutput>> initialConverter)
{
Data = data;
InitialConverter = x => new TOutput(); // start with a clean plate
var replace = initialConverter.Replace(initialConverter.Parameters[0], InitialConverter.Parameters[0]);
SinglePropertyToBinding = new Dictionary<MemberInfo, MemberAssignment>();
MemberAssignmentVisitor v = new MemberAssignmentVisitor(SinglePropertyToBinding);
v.Visit(initialConverter);
}
#endregion
public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
Expression<Func<TOutput, TConverted>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subPath,
Expression<Func<TProperty, TConverted>> subSelect)
{
//????
return this;
}
// this one works
public LinqDynamicConverter<TInput,TOutput> Select<TProperty>(
Expression<Func<TOutput, TProperty>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subSelect)
{
var miv = new MemberInfoVisitor();
miv.Visit(initializedOutputProperty);
var mi = miv.SingleProperty;
var param = InitialConverter.Parameters[0];
Expression<Func<TInput, TProperty>> replace = (Expression<Func<TInput, TProperty>>)subSelect.Replace(subSelect.Parameters[0], param);
var bind = Expression.Bind(mi, replace.Body);
SinglePropertyToBinding[mi] = bind;
return this;
}
public IQueryable<TOutput> Apply()
{
var converter = Expression.Lambda<Func<TInput, TOutput>>(
Expression.MemberInit((NewExpression)InitialConverter.Body, SinglePropertyToBinding.Values), InitialConverter.Parameters[0]);
return Data.Select(converter);
}
}
Found the solution. I needed to write a new Expression visitor that added the extra member acces(es):
/// <summary>
/// rebinds a full expression tree to a new single property
/// Example: we get x => x.Sub as subPath. Now the visitor starts visiting a new
/// expression x => x.Text. The result should be x => x.Sub.Text.
/// Traversing member accesses always starts with the rightmost side working toward the parameterexpression.
/// So when we reach the parameterexpression we have to inject the whole subpath including the parameter of the subpath
/// </summary>
/// <typeparam name="TConverted"></typeparam>
/// <typeparam name="TProperty"></typeparam>
private class LinqRebindVisitor<TConverted, TProperty> : ExpressionVisitor
{
public Expression<Func<TInput, TConverted>> ResultExpression { get; private set; }
private ParameterExpression SubPathParameter { get; set; }
private Expression SubPathBody { get; set; }
private bool InitialMode { get; set; }
public LinqRebindVisitor(Expression<Func<TInput, TProperty>> subPath)
{
SubPathParameter = subPath.Parameters[0];
SubPathBody = subPath.Body;
InitialMode = true;
}
protected override Expression VisitMember(MemberExpression node)
{
// Note that we cannot overwrite visitparameter because that method must return a parameterexpression
// So whenever we detect that our next expression will be a parameterexpression, we inject the subtree
if (node.Expression is ParameterExpression && node.Expression != SubPathParameter)
{
var expr = Visit(SubPathBody);
return Expression.MakeMemberAccess(expr, node.Member);
}
return base.VisitMember(node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
bool initialMode = InitialMode;
InitialMode = false;
Expression<T> expr = (Expression<T>)base.VisitLambda<T>(node);
if (initialMode)
{
ResultExpression = Expression.Lambda<Func<TInput, TConverted>>(expr.Body,SubPathParameter);
}
return expr;
}
}
The single property Select method is then rather trivial:
public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
Expression<Func<TOutput, TConverted>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subPath,
Expression<Func<TProperty, TConverted>> subSelect)
{
LinqRebindVisitor<TConverted, TProperty> rebindVisitor = new LinqRebindVisitor<TConverted, TProperty>(subPath);
rebindVisitor.Visit(subSelect);
var result = rebindVisitor.ResultExpression;
return Property<TConverted>(initializedOutputProperty, result);
}

Categories

Resources