如何从 Expression<Func<T, object[]>> 参数获取返回的属性

How to get returned properties from Expression<Func<T, object[]>> parameter

我正在做一个 .Net core 项目,目标是 .Net 5。 我有一个方法将接收一个参数,他的类型是 Expression<Func<T , object[]>>,在方法中我将循环表达式中的所有 returned 属性。

我的尝试:

public virtual void UpdateExcept( TEntity record, params Expression<Func<TEntity , object[]>>[] propertiesToBeExcluded )
{

   //Some logic here

    foreach ( var property in  propertiesToBeExcluded )
    {
        foreach ( var prop in property.GetMemberAccessList() )
        {
          //Here I got the property name (I think)
           var x = prop.Name;
        }
    }
}

在运行时我得到这个错误:

ArgumentException: The expression 'x => new [] {x.CreatedBy, Convert(x.CreatedOn, Object)}' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields, use an anonymous type: 't => new { t.MyProperty, t.MyField }'. (Parameter 'propertyAccessExpression')

更多说明: 实际上,我在基于 Entity framework 的存储库中创建了此方法,此方法应更新 TEntity 记录并忽略(不更新) propertiesToBeExcluded 中的某些已发送属性,有时我会更新记录并忽略一个 属性 并且在其他时候我将更新一条记录并忽略许多属性。

我试过的原方法逻辑:

public virtual void UpdateExcept( TEntity record, params Expression<Func<TEntity , object[]>>[] propertiesToBeExcluded )
{
    var entity = Context.Set<TEntity>();
    entity.Attach( record );
    Context.Entry( record ).State = EntityState.Modified;

    foreach ( var property in  propertiesToBeExcluded )
    {
        foreach ( var prop in property.GetMemberAccessList())
        {
            Context.Entry( record ).Property( prop.Name ).IsModified = false;
        }
    }
}

此方法的使用示例:

_studentRepository.UpdateExcept( record : student , propertiesToBeExcluded : x => new object[] {x.Picture} );

另一个例子:

_studentRepository.UpdateExcept( record : student , propertiesToBeExcluded : x => new object[] {x.CreatedOn, x.CreatedBy} );

这个方法在这个结构中:

public virtual void UpdateExcept( TEntity record, params Expression<Func<TEntity , object>>[] propertiesToBeExcluded )
{
    var entity = Context.Set<TEntity>();
    entity.Attach( record );
    Context.Entry( record ).State = EntityState.Modified;

    foreach ( var property in  propertiesToBeExcluded )
    {
        Context.Entry( record ).Property( property ).IsModified = false;
    }
}

旧结构的使用示例:

_studentRepository.UpdateExcept( record : student , propertiesToBeExcluded : x => x.CreatedOn, x => x.CreatedBy );

为什么我从旧结构改成新结构: 因为如果您注意到在旧结构中,我必须多次为 func 编写 x 的参数,而且我不知道如何一次使用它和 return 多个属性。

property.GetMemberAccessList()

我希望所有这些都可以帮助您理解问题,请就此问题提供帮助?

在评论中澄清后,您正在尝试使用 GetMemberAccessList 这是 内部 EF Core 方法,不应由最终用户代码直接使用。

但是假设您不关心/忽略该警告(这是一个警告,而不是错误),那么很高兴知道它不支持返回 属性 访问器数组的表达式,但是表达式返回 匿名类型 ,类似于许多 EF Core 流畅的 API 处理复合键、多 属性 索引等。因此预期的签名是单个可选的 Expression<Func<TEntity, object>>、w/o params 数组:

public virtual void UpdateExcept(
    TEntity record,
    Expression<Func<TEntity, object>> propertiesToBeExcluded = null)
{
    var entity = Context.Set<TEntity>();
    entity.Attach(record);
    Context.Entry(record).State = EntityState.Modified;

    // The modified code
    if (propertiesToBeExcluded != null)
    {
        foreach (var property in propertiesToBeExcluded.GetMemberAccessList())
        {
            Context.Entry(record).Property(property).IsModified = false;
        }
    }
}

和用法类似

x => x.CreatedOn

x => new { x.CreatedOn }

单人属性,

x => new { x.CreatedOn, x.CreatedBy }

多个属性。

如果您想使用该方法,这就是全部。


带有表达式返回表达式数组的方法签名(原始问题)不能使用该方法,需要完全不同的方法。

首先,Expression<Func<T, object[]>>可以有多种调用方式,完全不涉及属性或不直接创建数组,例如

x => new object[] { 1, "abc", new DateTime() }

x => Enumerable.Range(1, 3).Select(i => (object)i).ToArray()

所有这些都是有效的调用,因此显然您必须对调用方式施加一些限制。假设预期的呼叫就像您的示例中一样。所以你唯一需要意识到的是

body是什么类型的表达式
x => new object[] {x.CreatedOn, x.CreatedBy}

lambda 表达式。如果您在编译时创建这样的表达式并在运行时使用调试器检查其内容,您可以很容易地看到。

你会发现这个表达式的类型是NewArrayExpression which has Expressions 属性。从这里开始应该很简单 - 转换主体并使用 Expressions 属性 从调用者那里获取包含 x.CreatedOnx.CreatedBy 等的列表。

像这样的自定义扩展方法的所有内容

public static IEnumerable<MemberInfo> GetMemberAccessList<T>(this Expression<Func<T, object[]>> source) =>
    (source.Body is NewArrayExpression newArr ? newArr.Expressions : throw new InvalidOperationException())
    .Select(e => source.Parameters[0].MatchSimpleMemberAccess<MemberInfo>(e));

它正在使用另一种内部 EF Core 方法,但可以不这样做。用它来实现有问题的所需方法。