使用IEnumerable <expression <func <t,object =“”>&gt;&gt;在方法</expression <func <t,>

时间:2014-06-02 16:37:35

标签: c# entity-framework

我有以下方法:

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

我正在传递一个Entity Framework实体,附加它并将每个属性设置为已修改。

我想用它如下:

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

所以我传递了一个文件并说数据和名称属性已被修改。

但我收到错误:

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

如上所述,我应该如何更改方法以便能够使用它?

或者也许:

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

甚至:

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

3 个答案:

答案 0 :(得分:11)

看起来你真的想要:

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

然后将其命名为:

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

(请注意,我在此处使用类型推断,而不是明确使用_repository.Update<File>(...)。)

params部分是如何指定要转换为数组的多个参数。根本不需要匿名类型。如果你真的想要一个匿名类型,你可以通过反射访问它的成员 - 但这将是非常丑陋的,我怀疑你也需要转换每个lambda表达式(否则编译器将不会#39;能够推断出它的类型。)

答案 1 :(得分:3)

编写方法的签名是为了接受一系列属性选择器,而不是包含两个属性的匿名类型,每个属性都是属性选择器,或者是一个属性选择器,它选择一个包含对象的多个属性的匿名对象。

但语法相似;创建一个隐式类型的数组而不是一个匿名对象:

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

如果您希望能够将每个lambda指定为单独的参数,那么您需要更改方法以使用params作为该参数:

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

在更改之后,以下调用将起作用:

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

为了让您的解决方案能够选择使用所有成员的匿名类型,我们将在我们面前做更多的工作。要做到这一点,我们需要创建一个表达式访问者,它通过整个表达式查看成员访问,拉出正在访问参数成员的那些(因为这些是我们关心的)并将所有这些存储在一起。我们可以继承ExpressionVisitor来合理地做到这一点,但通常有必要创建一个扩展方法来改进使用它的语法。

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;
}

我们现在可以在方法中的选择器上调用此方法来提取我们关心的成员访问。除了遍历整个树并拉出所有成员访问(如果有多个)之外,如果有人创建的选择器不仅仅是成员访问,它也不会中断并抛出异常,正如你的代码目前所做的那样(使其有些脆弱)。

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;
}

答案 2 :(得分:1)

对于这个,

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

我能想到

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;
}

这应该可以解决问题,但它不会像Expression<Func<T, Object>>解决方案那么快。

并且有可能用

“欺骗”该方法
_repository.Update(file, x => new { DisplayName = x.Name });

另一个:

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

方法:

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