Rewriting Linq Select to a new subpath - c#

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

Related

Accessing nested properties and collections using Expression

I have the following classes:
public class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("profession")]
public Profession Profession { get; set; }
[JsonProperty("hobbies")]
public List<string> Hobbies { get; set; }
}
public class Profession
{
[JsonProperty("name")]
public string ProfessionName { get; set; }
[JsonProperty("industry")]
public string Industry { get; set; }
[JsonProperty("salary")]
public string AverageSalary { get; set; }
[JsonProperty("activities")]
public List<WorkActivity> WorkActivities { get; set; }
}
public class WorkActivity
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("rooms")]
public List<string> Rooms { get; set; }
}
public class PropertiesVisitor : ExpressionVisitor
{
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter)
{
param = parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == param)
{
Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
}
return base.VisitMember(node);
}
}
I call the methods in my app like this:
private static List<string> FindPropertyNames<T>(Expression<Func<T, object>> e)
{
var visitor = new PropertiesVisitor(e.Parameters[0]);
visitor.Visit(e);
return visitor.Names;
}
public static void Main(string[] args)
{
var names = FindPropertyNames<Person>(x => new { x.Age, x.Country, x.Profession.AverageSalary, x.Hobbies });
Console.WriteLine(string.Join(" ", names));
}
It works fine, except for one minor detailed - it doesn't check for nested properties. The output is the following: age country profession hobbies. I want it to be age country profession.salary hobbies.
I've been trying to fix the issue using a different approach, but I am unable to do so fully. I've tried the following:
public static MemberExpression GetMemberExpression(Expression e)
{
if (e is MemberExpression)
{
return (MemberExpression)e;
}
else if (e is LambdaExpression)
{
var le = e as LambdaExpression;
if (le.Body is MemberExpression)
{
return (MemberExpression)le.Body;
}
else if (le.Body is UnaryExpression)
{
return (MemberExpression)((UnaryExpression)le.Body).Operand;
}
}
return null;
}
public static string GetPropertyPath<T>(Expression<Func<T, object>> expr)
{
var path = new StringBuilder();
MemberExpression me = GetMemberExpression(expr);
do
{
if (path.Length > 0)
{
path.Insert(0, ".");
}
path.Insert(0, me.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
me = GetMemberExpression(me.Expression);
}
while (me != null);
return path.ToString();
}
It kind of does the job - but it can't take more than one property.
I call it like this:
var x = GetPropertyPath<Person>(p => p.Profession.AverageSalary);
Console.WriteLine(x);
I want to be able to send multiple properties, as in the first version. Also, I am unsure how to pass the following person.Profession.WorkActivities.Rooms as a parameter, because it is a list. I want to get profession.activities.rooms as output for it.
I would write a method that takes an Expression and returns all the members in the chain:
public static IEnumerable<MemberExpression> MemberClauses(this Expression expr) {
if (expr is not MemberExpression mexpr) {
yield break;
}
foreach (var item in MemberClauses(mexpr.Expression)) {
yield return item;
}
yield return mexpr;
}
Then, you can unwrap an entire method chain and LINQ to get the JSON property names:
public class PropertiesVisitor : ExpressionVisitor {
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter) => param = parameter;
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node) {
var chain = node.MemberClauses().ToList();
if (chain.Any() && chain.First().Expression == param) {
var name = string.Join(".", chain.Select(
mexpr => mexpr.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName
));
Names.Add(name);
return node;
} else {
return base.Visit(node);
}
}
}
and you can then get the property path as follows:
public static class Functions {
public static List<string> GetPropertyPath<T>(Expression<Func<Person, T>> expr) {
var visitor = new PropertiesVisitor(expr.Parameters[0]);
visitor.Visit(expr);
return visitor.Names;
}
}
var names = Functions.GetPropertyPath(p => new { p.Age, p.Country, p.Profession.AverageSalary, p.Hobbies });
foreach (var name in names) {
Console.WriteLine(name);
}

C# : Combine two generic expression trees

I have the two expression trees : one to get a navigation property of a class and another to filter on the values from this navigation property :
I am trying to combine them .
class Program
{
public interface IDeletable
{
bool IsDeleted { get; set; }
}
public class User
{
public int Id { get; set; }
public IEnumerable<BlogPost> BlogPosts;
}
public class BlogPost : IDeletable
{
public string Text { get; set; }
public bool IsDeleted { get; set; }
}
static void Main(string[] args)
{
var user = new User()
{
Id = 1,
BlogPosts = new List<BlogPost> {
new BlogPost {IsDeleted=false,Text="hello" },
new BlogPost {IsDeleted=true,Text="this is deleted" }
}
};
Expression<Func<User, IEnumerable<BlogPost>>> notDeletedExpression = Combine<User, BlogPost>(x => x.BlogPosts, x => !x.IsDeleted);
Console.ReadLine();
}
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
//TODO
// shourld return x=>x.Posts.Where(p=>IsDeleted==false) ;
return null;
}
}
In the sample below the two Expressions from your sample are combined using Enumerable.Where method:
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
// Consider caching the MethodInfo object:
var whereMethodInfo = GetEnumerableWhereMethodInfo<TChild>();
// Produce an Expression tree like:
// Enumerable.Where(<navigationProperty>, <filter>)
var filterExpr = Expression
.Call(
whereMethodInfo,
navigationProperty.Body,
filter
);
// Create a Lambda Expression with the parameters
// used for `navigationProperty` expression
return Expression
.Lambda<Func<T, IEnumerable<TChild>>>(
filterExpr,
navigationProperty.Parameters
);
}
private static MethodInfo GetEnumerableWhereMethodInfo<TSource>()
{
// Get a MethodInfo definition for `Enumerable.Where<>`:
var methodInfoDefinition = typeof(Enumerable)
.GetMethods()
.Where(x => x.Name == nameof(Enumerable.Where))
.First(x =>
{
var parameters = x.GetParameters();
return
parameters.Length == 2 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>);
});
// Get a MethodInfo object for `Enumerable.Where<TSource>`:
var methodInfo = methodInfoDefinition.MakeGenericMethod(typeof(TSource));
return methodInfo;
}

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

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.

Inserting a node/property in an Expression

I have an Expression where I like to insert one more node. I found this SO question similar question which adds a Property at the end of an Expression.
I'm quite new to Expressions and I can't figure out to do it between nodes.
I did this simple console test application to show the problem I have:
public class Program
{
private static void Main()
{
var provider = new Provider<Foo>();
var foo = new Foo {Label = "myLabel", Owner = "Me"};
provider.AddData(foo);
provider.AddExpression(f => f.Label);
// This writes: f => f.Label
Console.WriteLine(provider.OriginalExpression.ToString());
// This should write: f => f.WrappedData.Label
// At the moment it writes: NULL
Console.WriteLine(provider.WrapperExpression == null ? "NULL" : provider.WrapperExpression.ToString());
Console.ReadKey();
}
}
public class Foo
{
public string Label { get; set; }
public string Owner { get; set; }
}
public class Wrapper<T> where T : class
{
public string Id { get; set; }
public T WrappedData { get; set; }
}
public class Provider<T> where T : class
{
public Wrapper<T> _internalWrapper = new Wrapper<T>();
// The expression for the Property when using T (i.e. Foo)
public Expression<Func<T, string>> OriginalExpression { get; private set; }
// The expression for the Property when using _internalWrapper
public Expression<Func<Wrapper<T>, string>> WrapperExpression { get; private set; }
public void AddData(T data)
{
_internalWrapper = new Wrapper<T> { WrappedData = data, Id = "myId" };
}
public void AddExpression(Expression<Func<T, string>> valueExpression)
{
OriginalExpression = valueExpression;
BuildWrapperExpression();
}
private void BuildWrapperExpression()
{
// HERE IS THE PROBLEM:
// Here I have to insert the node "WrappedData" in the OriginalExpression and save it as WrapperExpression
// So {f => f.Label} must result into {f => f.WrappedData.Label}
// It should work as well for deeper nodes. I.e. when OriginalExpression is something like {f => f.Bar.Prop2.Label}
}
}
I've already tried a view versions in the BuildWrapperExpression() method but none delivers f => f.WrappedData.Label.
I get something like
f => Invoke(value (ConsoleApplication1.Provide1+<>c__DisplayClass1[ConsoleApplication1.Foo]).lambda, f.WrappedData) or
x => Invoke(f => f.Label, Invoke(e => e.WrappedData, x))
For my further use of the expression is has to be x => x.WrappedData.Label
Thanks a lot guys
Can't you simply:
private void BuildWrapperExpression()
{
var lambda = OriginalExpression.Compile();
WrapperExpression = x => lambda(x.WrappedData);
}
Alternatively, you can implement a ExpressionVistor. Check Marc's answer: https://stackoverflow.com/a/9132775/1386995
I came up with a great solution using the link Nikita provided.
I'm using this new class ExpressionChainer:
public static class ExpressionChainer
{
public static Expression<Func<TOuter, TInner>> Chain<TOuter, TMiddle, TInner>(
this Expression<Func<TOuter, TMiddle>> left, Expression<Func<TMiddle, TInner>> right)
{
return ChainTwo(left, right);
}
public static Expression<Func<TOuter, TInner>> ChainTwo<TOuter, TMiddle, TInner>(
Expression<Func<TOuter, TMiddle>> left, Expression<Func<TMiddle, TInner>> right)
{
var swap = new SwapVisitor(right.Parameters[0], left.Body);
return Expression.Lambda<Func<TOuter, TInner>>(
swap.Visit(right.Body), left.Parameters);
}
private class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
}
Then all I have to do in is:
private void BuildWrapperExpression()
{
Expression<Func<Wrapper<T>, T>> expression = x => x.WrappedData;
WrapperExpression = expression.Chain(OriginalExpression);
}
Which gives me x => x.WrappedData.Label as WrapperExpression

Categories

Resources