C# SetIfChanged-метод с выражениями

Мне приходится довольно много следовать С#-коду:

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);

Я думаю, что что-то подобное возможно с выражениями или лямбда-выражениями, но, если честно... я застрял в синтаксисе объявления и вызова такого метода.

Кто-нибудь может мне помочь?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Что ж, если вам действительно нужна проверка (скажем, в конце вы хотите знать, было ли что-то изменено или нет), вы можете использовать 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;

да, ты прав. К сожалению, я забыл одну вещь: в конце мне нужно знать, действительно ли что-то изменилось, поэтому мой метод должен вернуть флаг has-changed-flag.

suriel 15.12.2020 11:45

Я думаю, что это все еще не работает, потому что мне пришлось вызывать это для каждого поля newValue, но в любом случае, я думаю, что действительно пропустил одну вещь, которая сделала весь мой вопрос бессмысленным, поэтому спасибо за вашу помощь и ИЗВИНИТЕ

suriel 15.12.2020 12:26

нет проблем, пожалуйста, проверьте мое окончательное редактирование ответа.

Ashkan Mobayen Khiabani 15.12.2020 12:29
Ответ принят как подходящий

К сожалению, в 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, но это, как правило, довольно быстро, и кеширование, вероятно, того не стоит.

Другие вопросы по теме