如何构建属性选择器?

How to build a properties selector?

我正在尝试使用以下格式创建更新函数:

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

如何读取选择器中选择的属性?我需要属性的名称。

这是因为我想让更新功能更智能,所以它只更新必须更新的内容。它用于 sql 并且它是一个存储库函数。

谢谢。

澄清更新:

我希望能够像这样调用更新:

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

所以 IEnumerable of expressions 不是一个选项,但如果上面的更难,我会这样做...

假设您不想验证表达式实际上 return 一个 属性 值,这会起作用:

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

    // ...
}

否则你需要类似于此的东西 comprehensive answer


更新

如果你想使用匿名对象,那么你可以这样做:

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

    // ...
}

虽然这很容易被误用。

如果您不介意将更新代码写成 Update(item, x => x.Property1, x=> x.Property2 ),它的可读性会更好,而且更容易编写,如 Johnathans 的回答所示。

尽管如果添加泛型,则可以强制 x 为同一类型并将其重新用于 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...
}

注意,辅助函数是一个基本的,你可以使用更扩展的可重用辅助函数来获取名称,周围有很多

示例代码(第一个匿名对象只是为了创建一个示例对象): Update(new { foo = "a", bar = 5}, x=>x.foo, x=>x.bar);

现在有趣的是,您可以通过删除 new 来强制使用单个表达式,或者您可以组合该函数,并且仍然允许匿名,方法是在辅助函数中添加对 NewExpression 的检查(当然要更改它以允许使用多个名称):

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
}

这样你就可以选择(甚至混合)了。这两个调用产生相同的结果:

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

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