用于c#模型的HasChanged方法

时间:2018-08-30 14:45:41

标签: c# .net .net-core

我正在寻找一个可以询问模型属性是否已更改的解决方案。但是我想避免为所有模型及其所有属性编写自己的setter方法。

我想用它来自动生成基于模型的更新查询,并在那里更改属性。但是,如果我的模型具有默认值为Test的布尔属性false,则我无法区分该值是来自请求有效负载还是默认值。

我已经看到了INotifyPropertyChanged的实现,但是我也必须为所有属性编写一个setter。

public class Main
{
    public static void main()
    {
        var person = new Person();

        Console.WriteLine(person.HasChanged("Firstname")); // false
        Console.WriteLine(person.HasChanged("Lastname")); // false
        Console.WriteLine(person.HasChanged("LikesChocolate")); // false

        person.Firstname = "HisFirstname";
        person.LikesChocolate = true;

        Console.WriteLine(person.HasChanged("Firstname")); // true
        Console.WriteLine(person.HasChanged("Lastname")); // false
        Console.WriteLine(person.HasChanged("LikesChocolate")); // true
    }
}

public class Person : BaseModel
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public bool LikesChocolate { get; set; }
}

public class BaseModel
{
    public bool HasChanged(string propertyName)
    {
        // ...
    }
}

3 个答案:

答案 0 :(得分:0)

INotifyPropertyChanged是此类需求的既定惯例。保持代码可维护性的一部分是保持代码可预测性,并采用最佳实践和模式。

我不建议使用的替代方法是使用反射来遍历所有属性并动态添加属性更改的事件处理程序。然后,此处理程序可以设置一个布尔标志,该标志可以由HasChanges方法返回。请参考以下内容以了解具体问题:AddEventHandler using reflection

我建议您避免不必要的复杂性,并在设置器中坚持使用PropertyChanged通知。

答案 1 :(得分:0)

我可能会重复使用WPF的INotifyPropertyChanged模式的想法,并针对当前需求对其进行一些简化。但是,由于您仍需要编写设置器,因此只能部分解决问题。但是至少,您不需要单独管理每个属性。

因此,解决方案将是这样的:

void Main()
{
    var person = new Person();

    Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // false
    Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
    Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // false

    person.FirstName = "HisFirstname";
    person.LikesChocolate = true;

    Console.WriteLine(person.HasChanged(nameof(Person.FirstName))); // true
    Console.WriteLine(person.HasChanged(nameof(Person.LastName))); // false
    Console.WriteLine(person.HasChanged(nameof(Person.LikesChocolate))); // true
}

public class Person : ChangeTrackable
{
    private string _firstName;
    private string _lastName;
    private bool _likesChocolate;

    public string FirstName
    {
        get { return _firstName; }
        set { SetProperty(ref _firstName, value); }
    }

    public string LastName
    {
        get { return _lastName; }
        set { SetProperty(ref _lastName, value); }
    }

    public bool LikesChocolate
    {
        get { return _likesChocolate; }
        set { SetProperty(ref _likesChocolate, value); }
    }
}

public class ChangeTrackable
{
    private ConcurrentDictionary<string, bool> _changes =
       new ConcurrentDictionary<string, bool>();

    public bool HasChanged(string propertyName)
    {
        return _changes.TryGetValue(propertyName, out var isChanged)
                   ? isChanged : false;
    }

    public void ResetChanges()
    {
       _changes.Clear();
    }

    protected void SetProperty<T>(
        ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (!Equals(storage, value))
        {
            _changes[propertyName] = true;
        }
    }
}

ChangeTrackable跟踪属性是否已更改,并且在没有任何保证高性能的反射的情况下进行更改。请注意,如果在构造对象后使用一些实际值初始化属性,则使用此实现需要调用ResetChanges。缺点是您需要编写每个属性及其后备字段并调用SetProperty。另一方面,您可以确定要跟踪的内容,这将来在您的应用程序中会很方便。同样,我们也不需要将属性写为字符串(这要归功于编译时的CallerMemberNamenameof),从而简化了重构。

答案 2 :(得分:0)

作为我的评论的后续行动,一个概念证明(online):

using System.Reflection;

public class HasChangedBase
{
    private class PropertyState
    {
        public PropertyInfo Property {get;set;}
        public Object Value {get;set;}
    }

    private Dictionary<string, PropertyState> propertyStore;

    public void SaveState()
    {
        propertyStore = this
            .GetType()
            .GetProperties()
            .ToDictionary(p=>p.Name, p=>new PropertyState{Property = p, Value = p.GetValue(this)});
    }

    public bool HasChanged(string propertyName)
    {
        return propertyStore != null
            && propertyStore.ContainsKey(propertyName)
            && propertyStore[propertyName].Value != propertyStore[propertyName].Property.GetValue(this);
    }
}

public class POCO : HasChangedBase
{
    public string Prop1 {get;set;}
    public string Prop2 {get;set;}
}

var poco = new POCO();
poco.Prop1 = "a";
poco.Prop2 = "B";

poco.SaveState();
poco.Prop2 = "b";

poco.HasChanged("Prop1");
poco.HasChanged("Prop2");

请注意,广泛使用反射可能会降低应用程序的性能。