2

I am trying to create a generic "update expression builder" - an object that can be passed around, and used to indicate which fields need to be assigned what value. What I did is :

public class UpdateExpression<TClass> : Dictionary<Expression<Func<TClass, object>>, object> 
{};

public class  UpdateExpressionBuilder<TClass>
{
    private UpdateExpression<TClass> fieldsValues;

    public UpdateExpressionBuilder()
    {
        fieldsValues = new UpdateExpression<TClass>();
    }

    public UpdateExpressionBuilder<TClass> Add<TField>(Expression<Func<TClass, TField>> field, TField value) 
    {
        fieldsValues.Add(field, value);
        return this;
    }

    public UpdateExpression<TClass> Build()
    {
        return fieldsValues;
    }
}

Which is meant to be used as:

var personUpdateExpression = new UpdateExpressionBuilder<Person>()
            .Add(p => p.FirstName, "David")
            .Add(p => p.MiddleName, "A")
            .Build();

And then I can send personUpdateExpression as a parameter to any method, for example a database update.

The problem is that the call fieldsValues.Add(field, value) does not compile. Error is:

error CS1503: Argument 1: cannot convert from
'System.Linq.Expressions.Expression<System.Func<TClass, TField>>' to
'System.Linq.Expressions.Expression<System.Func<TClass, object>>'

I tried adding constraints where TField : class, new() but this did not change anything.

When changing the signature of Add to Add(Expression<Func<TClass, object>> field, object value) It works perfectly. However then I loose the compile time type checking, so that Add(p => p.FirstName, 123) will compile, but fail on runtime.

Y.L
  • 694
  • 12
  • 26
  • 4
    It may be worth editing your question title - you're not converting delegates, you're converting expression trees. That's somewhat different. Additionally, it's not clear what you'd actually do with the resulting expression trees. I suspect you'll find that you don't *want* a `Func` as you'd need to convert back to the right field type. Perhaps you should just be storing a `List`? – Jon Skeet Jul 01 '18 at 11:24
  • 2
    (Additionally, you'll need to do work to convert the expression tree to *fetch* a value into one to *set* a value. It's probably doable, but I would make sure it works for what you intend to do before you go further.) – Jon Skeet Jul 01 '18 at 11:25

1 Answers1

1

I concur completely to what Daisy wrote in its comments... Still you asked for something:

public UpdateExpressionBuilder<TClass> Add<TField>(Expression<Func<TClass, TField>> field, TField value)
{
    var body = field.Body;

    // Boxing for value types
    if (typeof(TField).IsValueType)
    {
        body = Expression.Convert(body, typeof(object));
    }

    Expression<Func<TClass, object>> field2 = Expression.Lambda<Func<TClass, object>>(body, field.Parameters);

    fieldsValues.Add(field2, value);
    return this;
}

and then

var personUpdateExpression = new UpdateExpressionBuilder<Person>()
    .Add(p => p.FirstName, "David")
    .Add(p => p.MiddleName, "A")
    .Add(p => p.Age, 30)
    .Build();

Note that your example was string-only, but I've added an example for int.

In the end we rewrite the Expression. For reference types, nothing needs to be done other than changing the return type of the Func<>. For value types you need an explicit boxing.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Thanks. Regarding the usage of this, see: https://stackoverflow.com/questions/8107134/how-set-value-a-property-selector-expressionfunct-tresult – Y.L Jul 01 '18 at 13:26