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;
}
}
}
Related
As the title says, "How to cast TResult which can be an object or List<object> inside an Expression<Func<T,TResult?>?"
public Task ExplicitLoadAsync<TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where TProperty : class
{
var typeOfProp = typeof(TProperty);
if (typeOfProp.IsInterface && typeOfProp.GetGenericTypeDefinition() == typeof(ICollection<>))
{
var collectionExpression = propertyExpression as Expression<Func<T, IEnumerable<TProperty>>>;
if (collectionExpression is not null)
{
return _dbContext.Entry(entity).Collection(collectionExpression).LoadAsync();
}
}
return _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();
}
collectionExpression will always be null, the issue is that Reference() only accept IEnumerable<TProperty> and overload is not possible because it will always use the method with TProperty instead of Expression<Func<T, IEnumerable<TProperty>>> propertyExpression
The reason is the cast is wrong because it will be IEnumerable<IEnumerable<...>> How to solve it?
One way is to have two methods with different names, but I would like to skip that as that would require more refactoring in the whole code base.
This solution is based on Guru Stron's deleted answer
public async Task ExplicitLoadAsync<T, TProperty>(T entity, Expression<Func<T, IEnumerable<TProperty>>> propertiesExpression) where T : class where TProperty : class
=> await _dbContext.Entry(entity).Collection(propertiesExpression).LoadAsync();
public async Task ExplicitLoadAsync<T, TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where T : class where TProperty : class
=> await _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();
Here the trick is to use different parameter names:
Expression<Func<T, IEnumerable<TProperty>>> propertiesExpression
Expression<Func<T, TProperty?>> propertyExpression
which allows you to be able to call both overloads
ExplicitLoadAsync<Entity, string>(enity, e => e.SingleString);
ExplicitLoadAsync<Entity, string>(enity, propertiesExpression: e => e.CollectionOfStrings);
I just want to post another answer to solve my case without calling the method with parameter name from the answer of Peter Csala.
public Task ExplicitLoadAsync<TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where TProperty : class
{
var typeOfProp = typeof(TProperty);
if (typeOfProp.IsInterface && typeOfProp.GetGenericTypeDefinition() == typeof(ICollection<>))
{
var pathName = PropertyPath<T>.GetAsStr(propertyExpression);
return _dbContext.Entry(entity).Collection(pathName).LoadAsync();
}
return _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();
}
Here is the utility class:
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;
}
public static string GetAsStr<TResult>(Expression<Func<TSource, TResult>> expression)
{
return string.Join(".", Get(expression).Select(p => p.Name));
}
private sealed class PropertyVisitor : ExpressionVisitor
{
internal readonly List<MemberInfo> _path = new();
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member is not PropertyInfo)
{
throw new ArgumentException("The path can only contain properties", nameof(node));
}
_path.Add(node.Member);
return base.VisitMember(node);
}
}
}
I have the following problem in my lambda expression: I need to rename the property because it will be passed from entity to entity. In other words: I need to use the same expression in more than one query in different entities.
For example:
var expr = x => x.Id == converterId
To be
var expr = x => x.ConverterId == converterId
I have tried to do the following
var oldParam = expr.Parameters[0];
var newParam = Expression.Parameter(oldParam.Type, "ConverterId");
This code replaces x not Id`
This isn't trivial, but can be done my writing (subclassing) an ExpressionVisitor, and overriding VisitMember, making the substitution for a different Expression.Property, but using the original target expression (from the expression in the original lambda)
However, for simple cases, it is probably easier to forget that, and just build the expression-tree manually from first principles, rather than using a lambda.
The following shows both approaches:
using System;
using System.Linq.Expressions;
static class P
{
static void Main()
{
// the compiler-generated expression-tree from the question
Console.WriteLine(Baseline(42));
// build our own epression trees manually
Console.WriteLine(ByName(42, nameof(Foo.Id)));
Console.WriteLine(ByName(42, nameof(Foo.ConverterId)));
// take the compiler-generated expression tree, and rewrite it with a visitor
Console.WriteLine(Convert(Baseline(42), nameof(Foo.Id), nameof(Foo.ConverterId)));
}
static Expression<Func<Foo, bool>> Baseline(int converterId)
{
// note this uses a "captured variable", so the output
// looks uglier than you might expect
return x => x.Id == converterId;
}
static Expression<Func<Foo, bool>> ByName(int converterId, string propertyOrFieldName)
{
var p = Expression.Parameter(typeof(Foo), "x");
var body = Expression.Equal(
Expression.PropertyOrField(p, propertyOrFieldName),
Expression.Constant(converterId, typeof(int))
);
return Expression.Lambda<Func<Foo, bool>>(body, p);
}
static Expression<Func<Foo, bool>> Convert(
Expression<Func<Foo, bool>> lambda, string from, string to)
{
var visitor = new ConversionVisitor(from, to);
return (Expression<Func<Foo, bool>>)visitor.Visit(lambda);
}
class ConversionVisitor : ExpressionVisitor
{
private readonly string _from, _to;
public ConversionVisitor(string from, string to)
{
_from = from;
_to = to;
}
protected override Expression VisitMember(MemberExpression node)
{
if(node.Member.Name == _from)
{
return Expression.PropertyOrField(
node.Expression, _to);
}
return base.VisitMember(node);
}
}
}
class Foo
{
public int Id { get; set; }
public int ConverterId { get; set; }
}
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.
I am trying to use expression trees so that I can choose to map across to a DTO using entity framework in much the same way as the Include directive works on a DbSet (part of an open sorce project implementing OData).
The code below represents a test case.
Expression<Func<Bar, Bar>> mapBar = b => new Bar { BarInt = b.BarInt, BarString = b.BarString };
Expression<Func<Foo, Foo>> mapFoo = f => new Foo { FooInt = f.FooInt, B = null };
Expression<Func<Foo, Foo>> target = f => new Foo { FooInt = f.FooInt,
B = new Bar {
BarInt=f.B.BarInt, BarString = f.B.BarString
} };
note Foo.B was null, and has the mapBar expression inserted.
I have got as far as adding the navigation property using the following ExpressionVisitor
public class UpdateExpressionVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldExpr;
private readonly Expression _newExpr;
public UpdateExpressionVisitor(ParameterExpression oldExpr, Expression newExpr)
{
_oldExpr = oldExpr;
_newExpr = newExpr;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
if (node.Member.Name == _oldExpr.Name)
{
return node.Update(_newExpr);
}
Console.WriteLine(node);
return base.VisitMemberAssignment(node);
}
}
But I cant figure out how to alter the expression new Bar { BarInt = b.BarInt,..., to become new Bar { BarInt = f.B.BarInt,...
The function will need some kind of ExpressionVisitor like so
public class MergingVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldExpr;
private readonly ParameterExpression _newProp;
private readonly ParameterExpression _newParent;
public MergingVisitor(ParameterExpression oldExpr, ParameterExpression newProp, ParameterExpression newParent)
{
_oldExpr = oldExpr;
_newProp = newProp;
_newParent = newParent;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == _oldExpr)
{
/*!!what to do here!!*/
var ma = Expression.MakeMemberAccess(_newProp, node.Member);
return Expression.MakeMemberAccess(_newParent, ma.Member);
}
return base.VisitMember(node);
}
and they all need to be linked together with something like
public static Expression<Func<T, TMap>> MapNavProperty<T, TMap, U, UMap>(this Expression<Func<T, TMap>> parent, Expression<Func<U, UMap>> nav, string propName)
{
//concern 1 remap name of prop in nav - not sure if I should do this first
var parentInitVarName = parent.Parameters[0].Name;
var parentParam = Expression.Parameter(typeof(T), parentInitVarName);
var propParam = Expression.Parameter(typeof(U), propName);
var mergeVisitor = new MergingVisitor(nav.Parameters[0], propParam, parentParam);
var newNavBody = mergeVisitor.Visit(nav.Body); //as MemberExpression;
//concern 2 replace given property
var visitor = new UpdateExpressionVisitor(propParam, nav.Body);
return (Expression<Func<T, TMap>>)visitor.Visit(parent);
}
although at present the parentParam will fail in the Expression.MakeMemberAccess function as it is of type T (Foo in the above example), rather than type U.
How can I alter the variable name and types in the child property's lamda expression - thank you.
Update
The answers from Svick and Ivan Stoev are erudite in explanation and both work perfectly - the (passing) unit tests and code from both answers are up on GitHub here. Thank you so much to both of you - it is a shame I cannot tick 2 answers, so it comes down to Eeny, meeny, miny, moe.
var parentParam = Expression.Parameter(typeof(T), parentInitVarName);
This won't work, parameters in expressions are not identified by name, but by reference. What you need to do is just get the parent's parameter.
var propParam = Expression.Parameter(typeof(U), propName);
This doesn't make any sense to me. propName is the name of a property, so you need to use it to create member access expression, not parameter expression.
public MergingVisitor(ParameterExpression oldExpr, ParameterExpression newProp, ParameterExpression newParent)
What you need is to replace one expression (b) with another (f.B). For that, I would create:
public class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression _oldExpr;
private readonly Expression _newExpr;
public ReplaceVisitor(Expression oldExpr, Expression newExpr)
{
_oldExpr = oldExpr;
_newExpr = newExpr;
}
public override Expression Visit(Expression node)
{
if (node == _oldExpr)
{
return _newExpr;
}
return base.Visit(node);
}
}
public UpdateExpressionVisitor(ParameterExpression oldExpr, Expression newExpr)
It doesn't make any sense for oldExpr to be a parameter expression. What you need is just a string.
var visitor = new UpdateExpressionVisitor(propParam, nav.Body);
You need to actually use newNavBody here.
The whole MapNavProperty() will now look like this:
var parentParam = parent.Parameters.Single();
var propExpression = Expression.Property(parentParam, propName);
var mergeVisitor = new ReplaceVisitor(nav.Parameters.Single(), propExpression);
var newNavBody = mergeVisitor.Visit(nav.Body);
var visitor = new UpdateExpressionVisitor(propName, newNavBody);
return (Expression<Func<T, TMap>>)visitor.Visit(parent);
With these changes, your code will work.
I would personally use different helpers:
(1) For replacing parameter
static Expression ReplaceParameter(this LambdaExpression lambda, Expression target)
{
return lambda.Body.ReplaceParameter(lambda.Parameters.Single(), target);
}
static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
(2) For replacing member assignment value
static Expression ReplaceMemberAssignment(this Expression expression, MemberInfo member, Expression value)
{
return new MemberAssignmentReplacer { Member = member, Value = value }.Visit(expression);
}
class MemberAssignmentReplacer : ExpressionVisitor
{
public MemberInfo Member;
public Expression Value;
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
return node.Member == Member ? node.Update(Value) : base.VisitMemberAssignment(node);
}
}
With these helpers, the function in question would be something like this:
public static Expression<Func<T, TMap>> MapNavProperty<T, TMap, U, UMap>(this Expression<Func<T, TMap>> parent, Expression<Func<U, UMap>> nav, string propName)
{
var parameter = parent.Parameters[0];
var body = parent.Body.ReplaceMemberAssignment(
typeof(TMap).GetProperty(propName),
nav.ReplaceParameter(Expression.Property(parameter, propName))
);
return Expression.Lambda<Func<T, TMap>>(body, parameter);
}
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.