从 Class/object 以编程方式为 EF 核心创建 MemberInitExpression

Create MemberInitExpression programmatically from Class/object for EF core

使用 EntityFramework-Plus 更新方法我需要一个 MemberInitExpression 作为参数。

await _db.Users
    .Where(u => u.Username == user.Username)
    .UpdateAsync(x => new User { Username = "manfred" });

但是我想以编程方式从给定的用户对象(不仅仅是示例中的用户名)设置所有属性。

我想以编程方式执行此操作,以避免在开发周期的后期丢失用户对象上新添加的属性。

编辑:我试过类似的方法,但是这不起作用,我无法设置值

Type t = User.GetType();
PropertyInfo[] props = t.GetProperties();

ParameterExpression paramExp = Expression.Parameter(typeof(User));
NewExpression newHolder = Expression.New(typeof(User));
var memberBindings = new List<MemberBinding>();
foreach (var prop in props)
{
    var methodInfo = prop.GetMethod;
    //var binding = Expression.Bind(methodInfo, paramExp);
    var binding = Expression.Bind(methodInfo, Expression.Parameter(methodInfo.GetType(), methodInfo.Name));
    memberBindings.Add(binding);
}
MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        newHolder, memberBindings);

所以,你有变量

User user;

持有来自某处的 User 个实例。而你想要表达

(User x) => new User { Prop1 = user.Prop1, Prop2 = user.Prop2, ... }

这是带有参数 x 且正文确实为 MemberInitExpression 的 lambda 表达式。 new User对应NewExpression,里面的赋值是MemberBindingExpression.Bind 的第一个参数只是 PropertyInfo,第二个参数是 Expression.Property 应用于 constant 表达式,其中包含 user 变量值(User 个实例)。它必须是常量,因为它不是来自参数。

获取您的代码

IEnumerable<PropertyInfo> props = t.GetProperties();

它可以从 EF Core 元数据中获取,并且还必须进行调整以至少排除 PK 属性,但假设它包含要应用的 PropertyInfo 列表,则可以构建所需的绑定如下:

var memberBindings = props
    .Select(prop => Expression.Bind(
        prop, Expression.Property(Expression.Constant(user), prop)))
    .ToList();

或者,可以评估 user 变量属性并将其作为常量传递(不确定这是否会影响 Update 方法):

var memberBindings = props
    .Select(prop => Expression.Bind(
        prop, Expression.Constant(prop.GetValue(user), prop.PropertyType)))
    .ToList();

其余代码与原文相同post。 ToList 并不是真正需要的,可以从上面的代码中删除,因为 Expression.MemberInit 接受 IEnumerable<MemberBinding>.

感谢 Ivan Stoev 的回答,我创建了以下代码:

它处理 FK 和只读属性。 我必须另外为 Update 方法创建一个 Lambda 来接受 MemberInitExpression。

Type t = User.GetType();
PropertyInfo[] props = t.GetProperties();

ParameterExpression paramExp = Expression.Parameter(typeof(User));
NewExpression newHolder = Expression.New(typeof(User));

var memberBindings = props
    .Select(prop =>
    {
        if (!prop.CanWrite)
            return null;

        if (prop.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute)))
            return null;
        
        if (prop.Name == "SomeObject")
            return null;

        return Expression.Bind(
            prop, Expression.Constant(prop.GetValue(User), prop.PropertyType));
    })
    .Where(m => m != null)
    .ToList();

MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        newHolder, memberBindings);
Expression<Func<User, User>> lambda =
    Expression.Lambda<Func<User, User>>(memberInitExpression, paramExp);

var updates = await _db.Users
    .Where(c => c.Username == User.Username )
    .UpdateAsync(lambda);