0

I was trying to make an Update function with the following format:

public int Update(
        Item Item, 
        Expression<Func<Item, object>> selector)

How can I read what properties are selected within the selector? I need the names of the properties.

This is because I want to make the update function smarter so it updates only what has to be updated. It's for sql and it's a repository function.

Thanks.

Update for clarification:

I want to be able to call Update like this:

Update(item, x => new { x.Property1, x.Property2 })

So IEnumerable of expressions is not an option but if the above is a lot more difficult I'll do that...

2 Answers2

2

Assuming you don't want to verify that the expressions actually return a property value, this would work:

public int Update(Item item, params Expression<Func<Item, object>>[] selectors)
{
    var propertyNames = selectors
        .Select(expression => ((MemberExpression)expression.Body).Member.Name);

    // ...
}

Otherwise you would need to something akin to this comprehensive answer.


Update

If you want to use anonymous objects, then you could do something like this:

public int Update<TProps>(Item item, Expression<Func<Item, TProps>> selector)
{
    var propertyNames = typeof(TProps)
        .GetProperties()
        .Select(prop => prop.Name);

    // ...
}

Although this could be easily misused.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
1

If you don't mind writing the update code as Update(item, x => x.Property1, x=> x.Property2 ), it would be just as readable and a bit easier to write, as shown by Johnathans answer.

Although if you add a generic, you can force x to be of the same type and reuse it for different objects besides Item

int Update<T>(T item, params Expression<Func<T, object>>[] selectors)
{
    string getName(Expression e)
    {
        if(e is LambdaExpression l)
            return getName(l.Body);
        if(e is MemberExpression m)
            return m.Member.Name;
        if(e is UnaryExpression u)
            return getName( u.Operand);
        throw new NotImplementedException();
    }
    var names = selectors.Select(getName);

    //update code...
}

NB, the helper function is a basic one, you can use a more extended reusable helper function to get the name, plenty of those around

Example code (the first anonymous object is just to create an example object): Update(new { foo = "a", bar = 5}, x=>x.foo, x=>x.bar);

Now the funny thing is, you can either force a single expression by removing the new, or you could combine that function, and still allow anonymous, by adding a check for a NewExpression inside the helper function (changing it to allow for multiple names of course):

int Update<T>(T item, params Expression<Func<T, object>>[] selectors)
{
    IEnumerable<string> getName(Expression e)
    {
        if (e is LambdaExpression l)
            return getName(l.Body);
        if (e is MemberExpression m)
            return new[] { m.Member.Name };
        if (e is UnaryExpression u)
            return getName(u.Operand);
        if (e is NewExpression n) // <- to account for the anonymous object
            return n.Arguments.SelectMany(getName);
        throw new NotImplementedException();
    }
    var names = selectors.SelectMany(getName);

    // etc
}

That way, you can choose (or even mix). Both these calls produce the same:

Update(new { foo = "a", bar = 5}, x=>x.foo, x=>x.bar);

Update(new { foo = "a", bar = 5 }, x => new { x.foo, x.bar});
Me.Name
  • 12,259
  • 3
  • 31
  • 48