Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use IEnumerable<Expression<Func<T, Object>>> in method

I have the following method:

public void Update<T>(T entity, IEnumerable<Expression<Func<T, Object>>> properties)  
    where T : class 
{
    _context.Set<T>().Attach(entity);

    foreach (var property in properties)
        _context.Entry<T>(entity)
            .Property(((MemberExpression)property.Body).Member.Name)
            .IsModified = true;

} // Update

I am passing an Entity Framework entity, attaching it and Set each property as modified.

I would like to use it as follows:

_repository.Update<File>(file, new { x => x.Data, x => x.Name });

So I am passing a file and saying the Data and Name properties were modified.

But I am getting the error:

The best overloaded method match for 'Update<File>(File,
IEnumerable<System.Linq.Expressions.Expression<System.Func<File,Object>>>)' 
has some invalid arguments

How should I change my method to be able to use it as I mentioned?

Or maybe:

_repository.Update<File>(file, x => x.Data, x => x.Name);

Or even:

_repository.Update<File>(file, x => new { x.Data, x.Name });
like image 580
Miguel Moura Avatar asked Jun 02 '14 16:06

Miguel Moura


3 Answers

It looks like you really want:

public void Update<T>(T entity, params Expression<Func<T, Object>>[] properties)
    where T : class

and then call it as:

_repository.Update(file, x => x.Data, x => x.Name);

(Note that I'm using type inference here rather than explicitly using _repository.Update<File>(...).)

The params part is how you can specify multiple arguments to be converted into an array. There's no need for an anonymous type at all. if you really wanted an anonymous type, you could access its members by reflection - but that would be pretty ugly and I suspect you'd need to cast each lambda expression too (as otherwise the compiler wouldn't be able to infer its type).

like image 66
Jon Skeet Avatar answered Oct 21 '22 00:10

Jon Skeet


Your method's signature is written to accept a sequence of property selectors, not an anonymous type containing two properties, each of which are property selectors or a property selector that is selecting out an anonymous object containing several properties of your object.

The syntax is similar enough though; create an implicitly typed array rather than an anonymous object:

_repository.Update<File>(file, new[] { x => x.Data, x => x.Name });

If you want to be able to specify each of the lambdas as separate parameters then you'd need to alter your method to use params for that argument:

public void Update<T>(T entity, params Expression<Func<T, Object>>[] properties)

After that change the following call would work:

_repository.Update<File>(file, x => x.Data, x => x.Name);

To allow your solution for a single selector that selects out an anonymous type using all of the members to work we'll have a bit more work ahead of us. To do this we'll need to create an expression visitor that looks through an entire expression for member accesses, pulls out the ones that are accessing members of the parameter (because those are what we care about) and stores all of them together. We can inherit from ExpressionVisitor to do this reasonably easily, but it's generally worth it to create an extension method to improve the syntax for utilizing it.

internal class MemberAccesses : ExpressionVisitor
{
    private ParameterExpression parameter;
    public HashSet<MemberExpression> Members { get; private set; }
    public MemberAccesses(ParameterExpression parameter)
    {
        this.parameter = parameter;
        Members = new HashSet<MemberExpression>();
    }
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == parameter)
        {
            Members.Add(node);
        }
        return base.VisitMember(node);
    }
}

public static IEnumerable<MemberExpression> GetPropertyAccesses<T, TResult>(
    this Expression<Func<T, TResult>> expression)
{
    var visitor = new MemberAccesses(expression.Parameters[0]);
    visitor.Visit(expression);
    return visitor.Members;
}

We can now call this method on the selectors in your method to pull out the member accesses that we care about. In addition to traversing the entire tree and pulling out all member accesses if there are several, it also doesn't break and throw an exception if someone creates a selector that is anything other than just a member access, as your code currently does (making it somewhat fragile).

public void Update<T>(
    T entity, params Expression<Func<T, Object>>[] selectors)
    where T : class
{
    _context.Set<T>().Attach(entity);

    var members = selectors.SelectMany(
        selector => selector.GetPropertyAccesses());
    foreach (var member in members)
        _context.Entry<T>(entity)
            .Property(member.Member.Name)
            .IsModified = true;
}
like image 37
Servy Avatar answered Oct 21 '22 00:10

Servy


For this one,

_repository.Update(file, x => new { x.Data, x.Name });

I could think of

public void Update<T>(T entity, Func<T, object> modify)
{
    foreach (PropertyInfo p in modify(entity).GetType().GetProperties())
        _context.Entry<T>(entity).Property(p.Name).IsModified = true;
}

This should do the trick, but it won't be as fast as an Expression<Func<T, Object>> solution.

And there is a possibility to "trick" the method with

_repository.Update(file, x => new { DisplayName = x.Name });

Another one:

_repository.Update(file, new { file.Data, file.Name });

the method:

public void Update<T>(T entity, object modify)
{
    foreach (PropertyInfo p in modify.GetType().GetProperties())
        _context.Entry<T>(entity).Property(p.Name).IsModified = true;
}
like image 2
Jürgen Steinblock Avatar answered Oct 21 '22 00:10

Jürgen Steinblock