从 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
,里面的赋值是MemberBinding
。 Expression.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);
使用 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
,里面的赋值是MemberBinding
。 Expression.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);