Мне приходится довольно много следовать С#-коду:
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
Да, проверка нужна, потому что иначе база данных все увидит как измененные столбцы.
Поля для установки являются автоматическими свойствами:
public class Part
{
[MaxLength(36), MinLength(1)]
public string Number { get; set; } = null!;
[MaxLength(80)]
public string Designation { get; set; } = null!;
}
и я не хочу писать явный установщик для каждого поля, который, конечно, может выполнять проверку перед установкой. Итак, я подумал о каком-то методе «SetIfChanged», который вызывается так, чтобы сделать код более читабельным и менее подверженным ошибкам:
//Options
dbPart.SetIfChanged(dbPart.Number, this.Number);
dbPart.SetIfChanged(dbPart.Number = this.Number);
dbPart.SetIfChanged(Number, this.Number);
Я думаю, что что-то подобное возможно с выражениями или лямбда-выражениями, но, если честно... я застрял в синтаксисе объявления и вызова такого метода.
Кто-нибудь может мне помочь?
Что ж, если вам действительно нужна проверка (скажем, в конце вы хотите знать, было ли что-то изменено или нет), вы можете использовать Reflection и перебирать свойства. но в вашем случае проверка не нужна.
взять это, например:
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;
Я думаю, что это все еще не работает, потому что мне пришлось вызывать это для каждого поля newValue, но в любом случае, я думаю, что действительно пропустил одну вещь, которая сделала весь мой вопрос бессмысленным, поэтому спасибо за вашу помощь и ИЗВИНИТЕ
нет проблем, пожалуйста, проверьте мое окончательное редактирование ответа.
К сожалению, в C# отсутствует ряд вещей, которые помогут вам в этом (например, свойства ref
или методы расширения для ссылочных объектов), но вы можете использовать Reflection, чтобы помочь вам в этом. Однако это, вероятно, будет довольно медленным.
С помощью метода, который принимает лямбду, вы можете написать метод 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
. Одним из способов ускорить это было бы передать вместо этого лямбду сеттера и геттера, но это приводит к такому же дублированию, как и исходный код.
Если вы хотите вместо этого передать объект и имя свойства, вы можете использовать:
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
, но это, как правило, довольно быстро, и кеширование, вероятно, того не стоит.
да, ты прав. К сожалению, я забыл одну вещь: в конце мне нужно знать, действительно ли что-то изменилось, поэтому мой метод должен вернуть флаг has-changed-flag.