I have this method and parameter.
void SomeMethod(Expression<Func<Products, bool>> where)
I call this method like this;
int i = 9;
SomeMethod(x=>x.Id==i)
And I want it to produce this string;
"x=>x.Id==9"
If I just print out the above expression as it is it will give me this string:
"x => (x.Id == value(isTakibi.WepApp.Controllers.HomeController+<>c__DisplayClass4_0).i)"
but I need "x.Id == 9". I need to evaluate the value of the variable i so that the result will be "x.id==9".
The way to simplify an expression in general is to compile it and execute the compiled delegate. Now you can't do that for any expression that still has any parameter expressions in it, because you don't know what the parameter's value will be (yet). This means we have two fundamental steps, first, determine which of the sub-expressions in our tree actually contain a parameter somewhere within that sub-tree, then evaluate all of the ones that don't.
So the first step is to determine which expressions contain a parameter within them. To do that we create an expression visitor that has a field indicating whether it's currently inside of a sub-tree with a parameter which then recursively checks its children, then checks itself, and then combines the results, adding all parameterless expressions into a collection while along the way.
private class ParameterlessExpressionSearcher : ExpressionVisitor
{
public HashSet<Expression> ParameterlessExpressions { get; } = new HashSet<Expression>();
private bool containsParameter = false;
public override Expression Visit(Expression node)
{
bool originalContainsParameter = containsParameter;
containsParameter = false;
base.Visit(node);
if (!containsParameter)
{
if (node?.NodeType == ExpressionType.Parameter)
containsParameter = true;
else
ParameterlessExpressions.Add(node);
}
containsParameter |= originalContainsParameter;
return node;
}
}
Next to evaluate the sub-expressions that have no parameter we need another visitor. This one just has to check if the expression is in the set of expressions we found with the previous visitor, and if so, compiles that expression into a parameterless delegate and executes it, otherwise it'll check its children to see if any of them can be replaced.
private class ParameterlessExpressionEvaluator : ExpressionVisitor
{
private HashSet<Expression> parameterlessExpressions;
public ParameterlessExpressionEvaluator(HashSet<Expression> parameterlessExpressions)
{
this.parameterlessExpressions = parameterlessExpressions;
}
public override Expression Visit(Expression node)
{
if (parameterlessExpressions.Contains(node))
return Evaluate(node);
else
return base.Visit(node);
}
private Expression Evaluate(Expression node)
{
if (node.NodeType == ExpressionType.Constant)
{
return node;
}
object value = Expression.Lambda(node).Compile().DynamicInvoke();
return Expression.Constant(value, node.Type);
}
}
Now we just need a simple method to first execute the first searcher, then the second, and return the results, and provide an overload that casts the result to a generic expression:
public static class ExpressionExtensions
{
public static Expression Simplify(this Expression expression)
{
var searcher = new ParameterlessExpressionSearcher();
searcher.Visit(expression);
return new ParameterlessExpressionEvaluator(searcher.ParameterlessExpressions).Visit(expression);
}
public static Expression<T> Simplify<T>(this Expression<T> expression)
{
return (Expression<T>)Simplify((Expression)expression);
}
//all previously shown code goes here
}
Now you can write:
Expression<Func<Products, bool>> e = x => x.Id == i;
e = e.Simplify();
Console.WriteLine(e);
And it'll print:
"x => (x.Id == 9)"
Alternatively, if you just want to evaluate one particular expression, and you're willing to change how you write the expression in the first place to accommodate, this answer shows how you can write a method to indicate what sub-expressions should be evaluated, and to evaluate just those expressions. That would be useful if you want to evaluate some things and not others.
.ToString() works for me:
void SomeMethod(Expression<Func<Product, bool>> where)
{
Console.WriteLine(where.ToString());
}
When calling with
SomeMethod(x=>x.Id==9);
Outputs:
x => (x.Id == 9)
As others have noted, you can get some resemblance of the original expression by calling ToString() on it, but this only works for very simple implementations and won't work well with closures. The c# compiler does a lot of "magic" to make things like closures work in expressions, and the "<>DisplayClass" stuff you're seeing is the result of that. You'd need to implement a custom visitor to go through the expression and write out the c# (essentially a reverse compiler) to get back to the original.
It would probably look something like the following stub:
public sealed class ExpressionWriterVisitor : ExpressionVisitor
{
private TextWriter _writer;
public ExpressionWriterVisitor(TextWriter writer)
{
_writer = writer;
}
protected override Expression VisitParameter(ParameterExpression node)
{
_writer.Write(node.Name);
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_writer.Write('(');
_writer.Write(string.Join(',', node.Parameters.Select(param => param.Name)));
_writer.Write(')');
_writer.Write("=>");
Visit(node.Body);
return node;
}
protected override Expression VisitConditional(ConditionalExpression node)
{
Visit(node.Test);
_writer.Write('?');
Visit(node.IfTrue);
_writer.Write(':');
Visit(node.IfFalse);
return node;
}
protected override Expression VisitBinary(BinaryExpression node)
{
Visit(node.Left);
_writer.Write(GetOperator(node.NodeType));
Visit(node.Right);
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
// Closures are represented as a constant object with fields representing each closed over value.
// This gets and prints the value of that closure.
if (node.Member is FieldInfo fieldInfo && node.Expression is ConstantExpression constExpr)
{
WriteConstantValue(fieldInfo.GetValue(constExpr.Value));
}
else
{
Visit(node.Expression);
_writer.Write('.');
_writer.Write(node.Member.Name);
}
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
WriteConstantValue(node.Value);
return node;
}
private void WriteConstantValue(object obj)
{
switch (obj)
{
case string str:
_writer.Write('"');
_writer.Write(str);
_writer.Write('"');
break;
default:
_writer.Write(obj);
break;
}
}
private static string GetOperator(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal:
return "==";
case ExpressionType.Not:
return "!";
case ExpressionType.NotEqual:
return "!==";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.Or:
return "|";
case ExpressionType.OrElse:
return "||";
case ExpressionType.And:
return "&";
case ExpressionType.AndAlso:
return "&&";
case ExpressionType.Add:
return "+";
case ExpressionType.AddAssign:
return "+=";
case ExpressionType.Subtract:
return "-";
case ExpressionType.SubtractAssign:
return "-=";
default:
return "???";
}
}
}
Note in VisitMember where it has some logic to extract the value out of a closure.
This will print out "(x)=>x.Id==9":
static void Main(string[] args)
{
var i = 9;
Expression<Func<Product, bool>> where = x => x.Id == i;
new ExpressionWriterVisitor(Console.Out).Visit(where);
}
In your example, your code is doing the right thing. The problem is that the variable i is not declared const. The expression has to assume that the value of i might change before it is getting called. This code gives you the result you expect: const int i = 9;
Unfortunately, this approach probably won't work for method caching for the same reason. Your code also doesn't have a way to ensure that i doesn't change between the time the expression is declared and the time when it is evaluated.
You could try writing an ExpressionVisitor that tries to find closures like that and evaluate them, but I've never tried it myself.
Related
My generic repository method is declared like this:
public virtual async Task<List<T>> GetMany(Expression<Func<T, bool>> filterExpression)
I would like to investigate that filterExpression and check if T is of a certain type AND if so, check if there's a filter for a property called ShardKey. Also, if the type checks out but the filter is not present, I would like to add it.
How can I achieve this?
Using an ExpressionVisitor you can search the Expression tree to determine if a reference to the desired property is made. If you wanted to be more sure, you could test to see if it is in a certain type of logical test as well.
If you don't find the desired reference, you can add a parent test to the filterExpression.
Here is an example, assuming ShardKey is an int:
public virtual async Task<List<T>> GetMany<T>(Expression<Func<T, bool>> filterExpression) {
if (typeof(T) == typeof(CertainType) && !filterExpression.ReferencesMember<CertainType>("ShardKey")) {
var skTest = Expression.Equal(Expression.PropertyOrField(filterExpression.Parameters[0], "ShardKey"), Expression.Constant(7));
filterExpression = Expression.Lambda<Func<T,bool>>(Expression.AndAlso(skTest, filterExpression.Body), filterExpression.Parameters);
}
Here is the ExpressionVisitor implementation:
public static class ExpressionExt {
public static bool ReferencesMember<T>(this Expression ex, string memberName) {
var mi = typeof(T).GetMember(memberName);
if (mi.Length != 1)
throw new ArgumentException($"{memberName} ambiguous or not found for {typeof(T).Name}");
var mf = new MemberFinder(mi[0]);
mf.Visit(ex);
return mf.Found;
}
class MemberFinder : ExpressionVisitor {
MemberInfo mi;
public bool Found = false;
public MemberFinder(MemberInfo _mi) => mi = _mi;
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node) {
if (Found)
return node;
else
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression me) {
if (me.Member == mi)
Found = true;
return me;
}
}
}
Is it possible to dynamically rewrite an Expression<T>, replacing an element of T with another type?
For example, replacing the DocumentTypeA with DocumentTypeB in the following situations:
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeA, bool>> expression = m => m.AString == "I'm a string"
I'll need to make the decision about what type to use at runtime, rather than compile time.
It's also worth noting that DocumentTypeA and DocumentTypeB don't relate to each other, apart from their properties are identical.
The end result would be to reprocess them so they now look like
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id"
Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
Expression<Func<DocumentTypeB, bool>> expression = m => m.AString == "I'm a string"
So the actual comparison part of the expression remains unchanged, only the top-level type has changed.
You can use ExpressionVisitor to replace the type.
class ParameterRewriter<T, U> : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type.Equals(typeof(T)))
{
return Expression.Parameter(typeof(U), node.Name);
}
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is ParameterExpression paramExp && paramExp.Type.Equals(typeof(T)))
{
return Expression.MakeMemberAccess(
Expression.Parameter(typeof(U), paramExp.Name),
typeof(U).GetMember(node.Member.Name).Single());
}
return base.VisitMember(node);
}
protected override Expression VisitLambda<L>(Expression<L> node)
{
var parameters = node.Parameters.ToList();
var found = false;
for (var i = 0; i < parameters.Count; i++)
{
if (parameters[i].Type.Equals(typeof(T)))
{
parameters[i] = Expression.Parameter(typeof(U), parameters[i].Name);
found = true;
}
}
if (found)
{
return Expression.Lambda(node.Body, parameters);
}
return base.VisitLambda(node);
}
}
In this case, create an instance new ParameterRewriter<DocumentTypeA, DocumentTypeB>() and visit the original expression tree, you will get what you want. An extension method maybe more readable:
public static class ExpressionExtensions
{
public static Expression<Func<U, R>> RewriteParameter<T, U, R>(this Expression<Func<T, R>> expression)
{
var rewriter = new ParameterRewriter<T, U>();
return (Expression<Func<U, R>>)rewriter.Visit(expression);
}
}
The usage is simple:
Expression<Func<A, bool>> expA = x => x.Id == 1;
Expression<Func<B, bool>> expB = expA.RewriteParameter<A, B, bool>();
Use an interface that both classes inherit from and contains the properties that are identical in both of the classes e.g.
interface IDocumentTypes
{
string AString { get; set; } //indicates that both classes need to implement this
//etc...
}
class DocumentTypeA : IDocumentTypes
{
//your class
}
Then both of your classes can use the Expression when it implements the interface IDocumentTypes and still be strongly typed. The classes don't need to have anything in common other than implementing the properties/functions defined in the interface.
public IEnumerable<Table1> GetMatchingTable1(string param, double[] Thicknesses)
{
return DBContext.Table1.Where(c => c.Field1 == param
&& Thicknesses.Any(Thickness => Thickness >= c.MinThickness && Thickness <= c.MaxThickness))
.ToList();
}
Above query return the following exception. "Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries."
So far, all my research on the web for this error pointed toward replacing "ANY" with "CONTAINS". Here is one site where they fix the problem using this solution : http://blog.hompus.nl/2010/08/26/joining-an-iqueryable-with-an-ienumerable/
But in my case, "CONTAINS" doesn't seem usable since I check a RANGE with Min and Max.
How should this query be written to have a proper SQL Statement generated by LinqToEntity?
Thanks
You could try to build the query dynamically:
public IEnumerable<Table1> GetAllCoilLengthSettingsWithChilds(string param, double[] Thicknesses)
{
// Base query
var query = LinqKit.Extensions.AsExpandable(DBContext.Table1.Where(c => c.Field1 == param));
// All the various || between the Thickness ranges
var predicate = LinqKit.PredicateBuilder.False<Table1>();
foreach (double th in Thicknesses)
{
// Don't want a closure around th
double th2 = th;
predicate = predicate.Or(c => th2 >= c.MinThickness && th2 <= c.MaxThickness);
}
// This is implicitly in && with the other Where
query = query.Where(predicate);
return query.ToList();
}
The PredicateBuilder helps you build an || query. Take it from the LinqKit (source available)
I've tested it with 1000 parameters (but they where DateTime, and I didn't have other query pieces), and it seems to work. Note that the program uses another extension of LinqPad, AsExpandable, used to make the PredicateBuilder "trick" work. Note that I'm using EF 6.1.3, so your mileage may vary.
If you don't want to use LinqKit, I'm appending my version of PredicateBuilder. It doesn't require the use of AsExpandable(), but its syntax is slightly different:
public class PredicateBuilder<T>
{
// We share a single parameter for all the PredicatBuilder<T>
// istances. This isn't a proble, because Expressions are immutable
protected static readonly ParameterExpression Parameter = Expression.Parameter(typeof(T), "x");
protected Expression Current { get; set; }
// Returns an empty PredicateBuilder that, if used, is true
public PredicateBuilder()
{
}
// Use it like this: .Where(predicate) or .Any(predicate) or
// .First(predicate) or...
public static implicit operator Expression<Func<T, bool>>(PredicateBuilder<T> predicate)
{
if (object.ReferenceEquals(predicate, null))
{
return null;
}
// Handling of empty PredicateBuilder
Expression current = predicate.Current ?? Expression.Constant(true);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(current, Parameter);
return lambda;
}
public static implicit operator PredicateBuilder<T>(Expression<Func<T, bool>> expression)
{
var predicate = new PredicateBuilder<T>();
if (expression != null)
{
// Equivalent to predicate.Or(expression)
predicate.And(expression);
}
return predicate;
}
public void And(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.AndAlso(this.Current, expression2) : expression2;
}
public void Or(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.OrElse(this.Current, expression2) : expression2;
}
public override string ToString()
{
// We reuse the .ToString() of Expression<Func<T, bool>>
// Implicit cast here :-)
Expression<Func<T, bool>> expression = this;
return expression.ToString();
}
// Small ExpressionVisitor that replaces the ParameterExpression of
// an Expression with another ParameterExpression (to make two
// Expressions "compatible")
protected class ParameterConverter : ExpressionVisitor
{
public readonly ParameterExpression From;
public readonly ParameterExpression To;
public ParameterConverter(ParameterExpression from, ParameterExpression to)
{
this.From = from;
this.To = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == this.From)
{
node = this.To;
}
return base.VisitParameter(node);
}
}
}
public static class PredicateBuilder
{
// The value of source isn't really necessary/interesting. Its type
// is :-) By passing a query you are building to Create, the compiler
// will give to Create the the of the object returned from the query
// Use it like:
// var predicate = PredicateBuilder.Create<MyType>();
// or
// var predicate = PredicateBuilder.Create(query);
public static PredicateBuilder<T> Create<T>(IEnumerable<T> source = null)
{
return new PredicateBuilder<T>();
}
// Useful if you want to start with a query:
// var predicate = PredicateBuilder.Create<MyType>(x => x.ID != 0);
// Note that if expression == null, then a new PredicateBuilder<T>()
// will be returned (that by default is "true")
public static PredicateBuilder<T> Create<T>(Expression<Func<T, bool>> expression)
{
// Implicit cast to PredicateBuilder<T>
return expression;
}
}
Use it like:
var predicate = PredicateBuilder.Create(query);
and then everything is the same (but remove the LinqKit.Extensions.AsExpandable part)
Below is a simple demonstration code of my problem.
[TestClass]
public class ExpressionTests
{
[TestMethod]
public void TestParam()
{
Search<Student>(s => s.Id == 1L);
GetStudent(1L);
}
private void GetStudent(long id)
{
Search<Student>(s => s.Id == id);
}
private void Search<T>(Expression<Func<T, bool>> filter)
{
var visitor = new MyExpressionVisitor();
visitor.Visit(filter);
}
}
public class MyExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
Assert.AreEqual(1L, node.Value);
return base.VisitConstant(node);
}
}
TestParam method causes VisitConstant to be invoked on two different paths:
1. TestParam -> Search -> VisitConstant
In this execution path constant expression (1L) passed to Search method is a real constant value. Here, everything is OK, assert succeeds as expected. When VisitConstant is invoked via first path node.Value.GetType() is Int64 and its .Value is 1L.
2. TestParam -> GetStudent -> Search -> VisitConstant
In this execution path constant expression (id: 1L), is taken by GetStudent as an argument and passed to Search method inside a closure.
Problem
The problem is on the second execution path. When VisitConstant is invoked via second path node.Value.GetType() is MyProject.Tests.ExpressionTests+<>c__DisplayClass0 and this class has a public field named id (same as GetStudent method's argument) which has the value of 1L.
Question
How can I get id value in second path? I know about closures, what a DisplayClass is and why it is created at compile time etc. I am only interested in getting its field value.
One thing I can think of is, via reflection. With something like below but it does not seem neat.
node.Value.GetType().GetFields()[0].GetValue(node.Value);
Bonus Problem
While playing with the code for gettting id value I changed VisitConstant method like below (which will not solve my problem though) and get an exception saying "'object' does not contain a definition for 'id'"
Bonus Question
As dynamics are resolved at runtime and DisplayClass is created at compile time, why cannot we access its fields with dynamic? While below code works, I expected that code would work too.
var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
VisitConstant will not help here, as it receives a compiler constructed ConstantExpression that uses object of private anonymous class to store values lambda was closed over (The DisplayClassxxx)
Instead, we should override VisitMember method and inspect its MemberExpression that already has ConstantExpression as an inner Expression.
Here is working test with little reflection.
[TestClass]
public class UnitTest2
{
[TestMethod]
public void TestMethod2()
{
Search<Student>(s => s.Id == 1L);
GetStudent(1L);
}
private void GetStudent(long id)
{
Search<Student>(s => s.Id == id);
}
private void Search<T>(Expression<Func<T, bool>> filter)
{
var visitor = new MyExpressionVisitor2();
visitor.Visit(filter.Body);
}
}
//ExpressionVisitor
public class MyExpressionVisitor2 : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
switch (node.Expression.NodeType)
{
case ExpressionType.Constant:
case ExpressionType.MemberAccess:
{
var cleanNode = GetMemberConstant(node);
//Test
Assert.AreEqual(1L, cleanNode.Value);
return cleanNode;
}
default:
{
return base.VisitMember(node);
}
}
}
private static ConstantExpression GetMemberConstant(MemberExpression node)
{
object value;
if (node.Member.MemberType == MemberTypes.Field)
{
value = GetFieldValue(node);
}
else if (node.Member.MemberType == MemberTypes.Property)
{
value = GetPropertyValue(node);
}
else
{
throw new NotSupportedException();
}
return Expression.Constant(value, node.Type);
}
private static object GetFieldValue(MemberExpression node)
{
var fieldInfo = (FieldInfo)node.Member;
var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;
return fieldInfo.GetValue(instance);
}
private static object GetPropertyValue(MemberExpression node)
{
var propertyInfo = (PropertyInfo)node.Member;
var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;
return propertyInfo.GetValue(instance, null);
}
private static ConstantExpression TryEvaluate(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant)
{
return (ConstantExpression)expression;
}
throw new NotSupportedException();
}
}
Here is an article that explains how to do it, and includes code that does it. Basically, what you can do is to create an expression that represents just that subexpression, compile it to a delegate and then execute that delegate. (The article also explains how to identify subexpressions that can be evaluated, but I guess you're not interested in that.)
Using the code from the article, modifying your code to the following will work:
private void Search<T>(Expression<Func<T, bool>> filter)
{
new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}
As dynamics are resolved at runtime and DisplayClass is created at compile time, why cannot we access its fields with dynamic?
Because that DisplayClass is a private class nested inside ExpressionTests, so code inside MyExpressionVisitor can't access its members.
If you make MyExpressionVisitor a nested class inside ExpressionTests, dynamic will start working on the DisplayClass.
Anonymous types don't behave this way, because they are not emitted as nested private types.
I'm developing a API that uses lambda expressions to specify properties. I'm using this famous piece of code similar to this one (this is simplified and incomplete, just to make clear what I'm talking about):
public void Foo<T, P>(Expression<Func<T, P>> action)
{
var expression = (MemberExpression)action.Body;
string propertyName = expression.Member.Name;
// ...
}
To be called like this:
Foo((String x) => x.Length);
Now I would like to specify a property path by chaining property names, like this:
Foo((MyClass x) => x.Name.Length);
Foo should be able to split the path into its property names ("Name" and "Length"). Is there a way to do this with reasonable effort?
There is a somehow similar looking question, but I think they are trying to combine lambda expressions there.
Another question also is dealing with nested property names, but I don't really understand what they are talking about.
Something like this?
public void Foo<T, P>(Expression<Func<T, P>> expr)
{
MemberExpression me;
switch (expr.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expr.Body as UnaryExpression;
me = ((ue != null) ? ue.Operand : null) as MemberExpression;
break;
default:
me = expr.Body as MemberExpression;
break;
}
while (me != null)
{
string propertyName = me.Member.Name;
Type propertyType = me.Type;
Console.WriteLine(propertyName + ": " + propertyType);
me = me.Expression as MemberExpression;
}
}
I played a little with ExpressionVisitor:
public static class PropertyPath<TSource>
{
public static IReadOnlyList<MemberInfo> Get<TResult>(Expression<Func<TSource, TResult>> expression)
{
var visitor = new PropertyVisitor();
visitor.Visit(expression.Body);
visitor.Path.Reverse();
return visitor.Path;
}
private class PropertyVisitor : ExpressionVisitor
{
internal readonly List<MemberInfo> Path = new List<MemberInfo>();
protected override Expression VisitMember(MemberExpression node)
{
if (!(node.Member is PropertyInfo))
{
throw new ArgumentException("The path can only contain properties", nameof(node));
}
this.Path.Add(node.Member);
return base.VisitMember(node);
}
}
}
Usage:
var path = string.Join(".", PropertyPath<string>.Get(x => x.Length).Select(p => p.Name));
Old question, I know... but if it's only the names you need, an even simpler way to do it is:
expr.ToString().Split('.').Skip(1)
EDIT:
public class A
{
public B Property { get; set; }
}
public class B
{
public C field;
}
[Fact]
public void FactMethodName()
{
var exp = (Expression<Func<A, object>>) (x => x.Property.field);
foreach (var part in exp.ToString().Split('.').Skip(1))
Console.WriteLine(part);
// Output:
// Property
// field
}
I have a shared .NET standard DTO between client and server and expressions are a great way to create query strings that can be rebuilt and executed Api side.
A perfect way to create Type safe queries over the wire.
I digress, I needed a property path also
x => x.Siblings.Age
To produce a string like
"Siblings.Age"
I went with this
public static string GetMemberPath(MemberExpression me)
{
var parts = new List<string>();
while (me != null)
{
parts.Add(me.Member.Name);
me = me.Expression as MemberExpression;
}
parts.Reverse();
return string.Join(".", parts);
}
public static string GetPath<T, TProperty>(this Expression<Func<T, TProperty>> exp)
{
return string.Join(".", GetItemsInPath(exp).Reverse());
}
private static IEnumerable<string> GetItemsInPath<T, TProperty>(Expression<Func<T, TProperty>> exp)
{
if (exp == null)
{
yield break;
}
var memberExp = FindMemberExpression(exp.Body);
while (memberExp != null)
{
yield return memberExp.Member.Name;
memberExp = FindMemberExpression(memberExp.Expression);
}
}
I refactored these answers to use recursion, so no need for order reversal. I only need the property tree, so left out all but MemberExpressions. Should be simple to add functionality if need be.
public IEnumerable<string> PropertyPath<TModel, TValue>(
Expression<Func<TModel, TValue>> expression)
{
if (expression.Body is MemberExpression memberExpression)
return PropertyPathRecurse(memberExpression);
return Enumerable.Empty<string>();
}
private IEnumerable<string> PropertyPathRecurse(MemberExpression? expression)
{
if (expression is null)
return Enumerable.Empty<string>();
return PropertyPathRecurse(expression.Expression as MemberExpression)
.Append(expression.Member.Name);
}