Search code examples
c#entity-frameworklinqexpression-trees

How to build expression tree from string values


I've asked a specific question elsewhere, but after no response and some investigating I've got it down to something much more generic, but I'm still struggling to build an expression tree.

I'm using a third party library which does some mappings using an interface and extension methods. Those mappings are specified as an expression tree, what I want to do is build that expression tree up from string values.

The extension method signature:

public static T UpdateGraph<T>(this DbContext context, T entity, Expression<Func<IUpdateConfiguration<T>, object>> mapping = null, bool allowDelete = true) where T : class, new();

The interface IUpdateConfiguration is just a marker interface, but has the following extension methods:

public static class UpdateConfigurationExtensions
{
    public static IUpdateConfiguration<T> OwnedCollection<T, T2>(this IUpdateConfiguration<T> config, Expression<Func<T, ICollection<T2>>> expression);
    public static IUpdateConfiguration<T> OwnedCollection<T, T2>(this IUpdateConfiguration<T> config, Expression<Func<T, ICollection<T2>>> expression, Expression<Func<IUpdateConfiguration<T2>, object>> mapping);
    public static IUpdateConfiguration<T> OwnedEntity<T, T2>(this IUpdateConfiguration<T> config, Expression<Func<T, T2>> expression);
    public static IUpdateConfiguration<T> OwnedEntity<T, T2>(this IUpdateConfiguration<T> config, Expression<Func<T, T2>> expression, Expression<Func<IUpdateConfiguration<T2>, object>> mapping);
}
  

Using an example entity:

public class Person
{
  public Car Car {get;set;}
  public House House {get;set;}
}

So normal explicit usage is:

dbContext.UpdateGraph(person, mapping => mapping.OwnedEntity(p => p.House).OwnedEntity(p=> p.Car));

What I need to do is build up that mapping from a list of property names,

var props = {"Car","House"}

dbContext.UpdateGraph(person, buildExpressionFromStrings<Person>(props);

I've got so far:


static Expression<Func<IUpdateConfiguration<t>, object>> buildExpressionFromStrings<t>(IEnumerable<string> props)
{
   foreach (var s in props)
   {
        var single = buildExpressionFromString(s);
        somehow add this to chaining overall expression

    }      
}

static Expression<Func<IUpdateConfiguration<t>, object>> buildExpressionFromString<t>(string prop)
            {
                var ownedChildParam = Expression.Parameter(typeof(t));

                var ownedChildExpression = Expression.PropertyOrField(ownedChildParam, prop);

                var ownedChildLam = Expression.Lambda(ownedChildExpression, ownedChildParam);

                // Up to here I think we've built the (o => o.Car) part of map => map.OwnedEntity(o => o.Car)
// So now we need to build the map=>map.OwnedEntity(ownedChildLam) part, by calling Expression.Call I believe, but here I'm getting confused.
            }

In reality, the real-world code is more complex than this (needs to deal with recursion and child properties/mappings), but I think I can get that sorted once I get the expression built for one level. I've been tearing my hair out for over a day, trying to get this sorted... To give some context, I'm using entity framework, and some configuration to define aggregate roots.


Solution

  • Few things to mention. Here

    mapping => mapping.OwnedEntity(p => p.House).OwnedEntity(p=> p.Car)
    

    the parts of the lambda expression body are not lambda expressions, but just chained method call expressions, the first using the lambda expression parameter and the next using the previous result.

    Second, the extension method are just static method call C# sugar, and in expression trees they must be "called" as static methods.

    So, building a call to

    public static IUpdateConfiguration<T> OwnedEntity<T, T2>(this IUpdateConfiguration<T> config, Expression<Func<T, T2>> expression);
    

    could be like this

    static Expression BuildConfigurationCall<T>(Expression config, string propertyName)
    {
        var parameter = Expression.Parameter(typeof(T), "it");
        var property = Expression.Property(parameter, propertyName);
        var selector = Expression.Lambda(property, parameter);
        return Expression.Call(
            typeof(UpdateConfigurationExtensions),
            nameof(UpdateConfigurationExtensions.OwnedEntity),
            new [] { typeof(T), property.Type },
            config,
            selector);
    }
    

    and the lambda expression in question would be:

    static Expression<Func<IUpdateConfiguration<T>, object>> BuildConfigurationExpression<T>(IEnumerable<string> propertyNames)
    {
        var parameter = Expression.Parameter(typeof(IUpdateConfiguration<T>), "config");
        var body = propertyNames.Aggregate((Expression)parameter,
            (config, propertyName) => BuildConfigurationCall<T>(config, propertyName));
        return Expression.Lambda<Func<IUpdateConfiguration<T>, object>>(body, parameter);
    }