在方法中使用IEnumerable<Expression<Func<T, Object>>>

5
我有以下方法:

我有以下方法:

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个回答

21

看起来你真的想要:

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表达式进行强制转换(否则编译器无法推断其类型)。

我喜欢使用params方法,但是当我在我的foreach循环中使用时,会出现错误:错误41-foreach语句无法操作类型为'System.Linq.Expressions.Expression<System.Func<T,object>>'的变量,因为'System.Linq.Expressions.Expression<System.Func<T,object>>'不包含公共定义'GetEnumerator'。有可能解决这个问题吗? - Miguel Moura
@MDMoura:嗯,我看不到你在那里使用的代码,但你肯定可以遍历properties,因为它是一个数组... - Jon Skeet
我忘记在表达式后面添加 [] 了...这就是原因。现在它正常工作了。 - Miguel Moura
@SchlaWiener:没错 - 所以我对原始语法的反对仍然存在;你只是找到了另一种选择。 - Jon Skeet
我们如何扩展这个表达式树 Expression<Func<T, Object>>[],以便在每个对象中有另一个表达式数组?因为 EF Core 需要使用 Include()ThenInclude() 从 dbContext 中获取相关数据。所以我们需要传递一个父实体的数组,其中每个父实体可能有多个子实体引用。 - Gopinath
显示剩余6条评论

4

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

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

_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

对于这个,

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接