c# SetIfChanged-Method with 表达式
c# SetIfChanged-Method with Expressions
我必须经常遵循 C# 代码:
public void UpdateDB(Model.ResultContext db)
{
Model.Planning.Part dbPart = db.Parts.Where(/*someClause*/).FirstOrDefault();
//The awkward part
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
if (dbPart.NumberShort != NumberShort)
{
dbPart.NumberShort = NumberShort;
}
if (dbPart.Designation != Designation)
{
dbPart.Designation = Designation;
}
}
检查每个字段并将其包装在if != then set
中显然有点尴尬
是的,需要进行检查,否则数据库会将所有内容视为已更改的列。
要设置的字段是auto-Properties:
public class Part
{
[MaxLength(36), MinLength(1)]
public string Number { get; set; } = null!;
[MaxLength(80)]
public string Designation { get; set; } = null!;
}
而且我不想为每个字段都写一个明确的setter,这当然可以在设置之前进行检查。
所以我想到的是一些方法“SetIfChanged”,这样调用它可以使代码更易读,更少 error-prone:
//Options
dbPart.SetIfChanged(dbPart.Number, this.Number);
dbPart.SetIfChanged(dbPart.Number = this.Number);
dbPart.SetIfChanged(Number, this.Number);
我认为表达式或 lambda 可以实现类似的功能,但老实说...我坚持使用声明和调用此类方法的语法
有人能帮帮我吗?
好吧,如果您真的需要检查(假设最后您想知道是否有任何更改),您可以使用反射并循环遍历属性。但在你的情况下不需要检查。
以这个为例:
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
true) 如果值不同则设置新值
false)表示新值和旧值相同,重新设置也无妨
如果你想知道最后有没有什么变化:
bool changed = false;
var type = dbPart.GetType();
foreach(var (PropertyInfo)pi in type.GetProperties()
{
if(pi.GetValue(dbPart) != newValue)
{
changed = true;
pi.SetValue(dbPart, newValue);
}
}
或者您可以这样做:
bool changed = dbPart.Number != Number || dbPart.Designation != Designation;
dbPart.Number = Number;
dbPart.Designation = Designation;
不幸的是,C# 缺少很多东西来帮助您解决这个问题(例如 属性 ref
s 或引用对象的扩展方法),但是您可以使用反射来帮助解决这个问题。但是,它可能会很慢。
对于采用 lambda 的方法,您可以编写一个 set 方法:
public static void SetIfDifferent<T>(Expression<Func<T>> getterFnE, T newVal) {
var me = (MemberExpression)getterFnE.Body;
var target = me.Expression;
var targetLambda = Expression.Lambda(target);
var prop = me.Member;
var oldVal = getterFnE.Compile().Invoke();
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal)) {
var obj = targetLambda.Compile().DynamicInvoke();
prop.SetValue(obj, newVal);
}
}
这将像这样使用:
SetIfDifferent(() => dbPart.Number, Number);
SetIfDifferent(() => dbPart.NumberShort, NumberShort);
SetIfDifferent(() => dbPart.Designation, Designation);
这会很慢,因为需要编译 Expression
树并使用 DynamicInvoke
。加快速度的一种方法是传入 setter 和 getter lambda,但这会导致与原始代码一样多的重复。
如果您愿意传递 属性 的对象和名称,您可以使用:
public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);
public static void SetIfDifferent2<TObj, TField>(this TObj obj, string fieldName, TField newVal) {
var prop = typeof(TObj).GetProperty(fieldName);
var oldVal = prop.GetValue<TField>(fieldName);
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal))
prop.SetValue(obj, newVal);
}
你可以像这样使用:
dbPart.SetIfDifferent2(nameof(dbPart.Number), Number);
dbPart.SetIfDifferent2(nameof(dbPart.NumberShort), NumberShort);
dbPart.SetIfDifferent2(nameof(dbPart.Designation), Designation);
不幸的是,它需要重复 dbPart
,除非您愿意只输入字段名称(例如 "Number"
),但如果字段更改,这将导致运行时错误。
您也可以缓存 PropertyInfo
而不是使用 GetProperty
查找它,但这通常非常快,缓存可能不值得。
我必须经常遵循 C# 代码:
public void UpdateDB(Model.ResultContext db)
{
Model.Planning.Part dbPart = db.Parts.Where(/*someClause*/).FirstOrDefault();
//The awkward part
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
if (dbPart.NumberShort != NumberShort)
{
dbPart.NumberShort = NumberShort;
}
if (dbPart.Designation != Designation)
{
dbPart.Designation = Designation;
}
}
检查每个字段并将其包装在if != then set
中显然有点尴尬
是的,需要进行检查,否则数据库会将所有内容视为已更改的列。
要设置的字段是auto-Properties:
public class Part
{
[MaxLength(36), MinLength(1)]
public string Number { get; set; } = null!;
[MaxLength(80)]
public string Designation { get; set; } = null!;
}
而且我不想为每个字段都写一个明确的setter,这当然可以在设置之前进行检查。 所以我想到的是一些方法“SetIfChanged”,这样调用它可以使代码更易读,更少 error-prone:
//Options
dbPart.SetIfChanged(dbPart.Number, this.Number);
dbPart.SetIfChanged(dbPart.Number = this.Number);
dbPart.SetIfChanged(Number, this.Number);
我认为表达式或 lambda 可以实现类似的功能,但老实说...我坚持使用声明和调用此类方法的语法
有人能帮帮我吗?
好吧,如果您真的需要检查(假设最后您想知道是否有任何更改),您可以使用反射并循环遍历属性。但在你的情况下不需要检查。
以这个为例:
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
true) 如果值不同则设置新值
false)表示新值和旧值相同,重新设置也无妨
如果你想知道最后有没有什么变化:
bool changed = false;
var type = dbPart.GetType();
foreach(var (PropertyInfo)pi in type.GetProperties()
{
if(pi.GetValue(dbPart) != newValue)
{
changed = true;
pi.SetValue(dbPart, newValue);
}
}
或者您可以这样做:
bool changed = dbPart.Number != Number || dbPart.Designation != Designation;
dbPart.Number = Number;
dbPart.Designation = Designation;
不幸的是,C# 缺少很多东西来帮助您解决这个问题(例如 属性 ref
s 或引用对象的扩展方法),但是您可以使用反射来帮助解决这个问题。但是,它可能会很慢。
对于采用 lambda 的方法,您可以编写一个 set 方法:
public static void SetIfDifferent<T>(Expression<Func<T>> getterFnE, T newVal) {
var me = (MemberExpression)getterFnE.Body;
var target = me.Expression;
var targetLambda = Expression.Lambda(target);
var prop = me.Member;
var oldVal = getterFnE.Compile().Invoke();
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal)) {
var obj = targetLambda.Compile().DynamicInvoke();
prop.SetValue(obj, newVal);
}
}
这将像这样使用:
SetIfDifferent(() => dbPart.Number, Number);
SetIfDifferent(() => dbPart.NumberShort, NumberShort);
SetIfDifferent(() => dbPart.Designation, Designation);
这会很慢,因为需要编译 Expression
树并使用 DynamicInvoke
。加快速度的一种方法是传入 setter 和 getter lambda,但这会导致与原始代码一样多的重复。
如果您愿意传递 属性 的对象和名称,您可以使用:
public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);
public static void SetIfDifferent2<TObj, TField>(this TObj obj, string fieldName, TField newVal) {
var prop = typeof(TObj).GetProperty(fieldName);
var oldVal = prop.GetValue<TField>(fieldName);
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal))
prop.SetValue(obj, newVal);
}
你可以像这样使用:
dbPart.SetIfDifferent2(nameof(dbPart.Number), Number);
dbPart.SetIfDifferent2(nameof(dbPart.NumberShort), NumberShort);
dbPart.SetIfDifferent2(nameof(dbPart.Designation), Designation);
不幸的是,它需要重复 dbPart
,除非您愿意只输入字段名称(例如 "Number"
),但如果字段更改,这将导致运行时错误。
您也可以缓存 PropertyInfo
而不是使用 GetProperty
查找它,但这通常非常快,缓存可能不值得。