在对象上实现更改跟踪的最佳方法是什么

use*_*348 38 .net c#

我有一个包含5个属性的类.

如果任何值被分配给这些字段中的任何一个,则另一个值(例如IsDIrty)将改变为真.

public class Class1
{
    bool IsDIrty {get;set;}

    string Prop1 {get;set;}
    string Prop2 {get;set;}
    string Prop3 {get;set;}
    string Prop4 {get;set;}
    string Prop5 {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

Bin*_*ier 44

要做到这一点,你不能真正使用自动getter和setter,你需要在每个setter中设置IsDirty.

我通常有一个"setProperty"泛型方法,它接受ref参数,属性名称和新值.我在setter中调用它,允许我可以设置isDirty的单个点并提高Change通知事件,例如

protected bool SetProperty<T>(string name, ref T oldValue, T newValue) where T : System.IComparable<T>
    {
        if (oldValue == null || oldValue.CompareTo(newValue) != 0)
        {
            oldValue = newValue;
            PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
            isDirty = true;
            return true;
        }
        return false;
    }
// For nullable types
protected void SetProperty<T>(string name, ref Nullable<T> oldValue, Nullable<T> newValue) where T : struct, System.IComparable<T>
{
    if (oldValue.HasValue != newValue.HasValue || (newValue.HasValue && oldValue.Value.CompareTo(newValue.Value) != 0))
    {
        oldValue = newValue;
        PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 好的答案(+1).我唯一的建议是实现IChangeTracking(http://msdn.microsoft.com/en-us/library/system.componentmodel.ichangetracking.aspx),而不是创建自己的IsDirty属性. (25认同)
  • John Myczek:我完全没有意识到这个界面,我会立即切换我的宠物项目(或者只要那个讨厌的`life`东西允许).谢谢 :) (3认同)
  • 您应该使用`IEquatable`而不是`IComparable`,并且您不需要测试`oldValue`是否为null.只需使用`if(oldValue.Equals(newValue))return;`. (3认同)
  • 这里的事件调用不是线程安全的.你需要写`var handler = PropertyChanged; if(handler!= null)handler(this,e);` (3认同)

Shi*_*mmy 18

您可以实现现在包含在.NET Standard 2.0中的IChangeTrackingIRevertibleChangeTracking接口.

实施如下:

IChangeTracking:

class Entity : IChangeTracking
{
  string _FirstName;
  public string FirstName
  {
    get => _FirstName;
    set
    {
      if (_FirstName != value)
      {
        _FirstName = value;
        IsChanged = true;
      }
    }
  }

  string _LastName;
  public string LastName
  {
    get => _LastName;
    set
    {
      if (_LastName != value)
      {
        _LastName = value;
        IsChanged = true;
      }
    }
  }

  public bool IsChanged { get; private set; }    
  public void AcceptChanges() => IsChanged = false;
}
Run Code Online (Sandbox Code Playgroud)

IRevertibleChangeTracking:

class Entity : IRevertibleChangeTracking
{
  Dictionary<string, object> _Values = new Dictionary<string, object>();

  string _FirstName;
  public string FirstName
  {
    get => _FirstName;
    set
    {
      if (_FirstName != value)
      {
        if (!_Values.ContainsKey(nameof(FirstName)))
          _Values[nameof(FirstName)] = _FirstName;
        _FirstName = value;
        IsChanged = true;
      }
    }
  }

  string _LastName;
  public string LastName
  {
    get => _LastName;
    set
    {
      if (_LastName != value)
      {
        if (!_Values.ContainsKey(nameof(LastName)))
          _Values[nameof(LastName)] = _LastName;
        _LastName = value;
        IsChanged = true;
      }
    }
  }

  public bool IsChanged { get; private set; }

  public void RejectChanges()
  {
    foreach (var property in _Values)
      GetType().GetRuntimeProperty(property.Key).SetValue(this, property.Value);
    AcceptChanges();
  }

  public void AcceptChanges()
  {
    _Values.Clear();
    IsChanged = false;
  }
}
Run Code Online (Sandbox Code Playgroud)

我最喜欢的另一个选项是使用更改跟踪库,例如TrackerDog,它为您生成所有样板代码,同时只需要提供POCO实体.

如果您不想手动实现所有属性,还有更多方法可以实现此目的.一种选择是使用编织库,例如Fody.PropertyChangedFody.PropertyChanging,并处理更改方法以缓存旧值并跟踪对象状态.另一种选择是将对象的图形存储为MD5或其他一些哈希值,并在任何更改时重置它,您可能会感到惊讶,但如果您不期望有太多变化,并且如果您只是按需请求它,它可以真正起作用快速.

这是一个示例实现(注意:需要Json.NETFody/PropertyChanged:

[AddINotifyPropertyChangedInterface]
class Entity : IChangeTracking
{
  public string UserName { get; set; }
  public string LastName { get; set; }

  public bool IsChanged { get; private set; }

    string hash;
  string GetHash()
  {
    if (hash == null)
      using (var md5 = MD5.Create())
      using (var stream = new MemoryStream())
      using (var writer = new StreamWriter(stream))
      {
        _JsonSerializer.Serialize(writer, this);
        var hash = md5.ComputeHash(stream);
        this.hash = Convert.ToBase64String(hash);
      }
    return hash;
  }

  string acceptedHash;
  public void AcceptChanges() => acceptedHash = GetHash();

  static readonly JsonSerializer _JsonSerializer = CreateSerializer();
  static JsonSerializer CreateSerializer()
  {
    var serializer = new JsonSerializer();
    serializer.Converters.Add(new EmptyStringConverter());
    return serializer;
  }

  class EmptyStringConverter : JsonConverter
  {
    public override bool CanConvert(Type objectType) 
      => objectType == typeof(string);

    public override object ReadJson(JsonReader reader,
      Type objectType,
      object existingValue,
      JsonSerializer serializer)
      => throw new NotSupportedException();

    public override void WriteJson(JsonWriter writer, 
      object value,
      JsonSerializer serializer)
    {
      if (value is string str && str.All(char.IsWhiteSpace))
        value = null;

      writer.WriteValue(value);
    }

    public override bool CanRead => false;  
  }   
}
Run Code Online (Sandbox Code Playgroud)


And*_*lam 8

Dan的解决方案非常完美.

另一个选择是考虑你是否必须在多个类上执行此操作(或者您希望外部类"监听"属性的更改):

  • INotifyPropertyChanged在抽象类中实现接口
  • IsDirty属性移动到抽象类
  • 拥有Class1和所有其他需要此功能的类来扩展您的抽象类
  • 让所有的setter都触发PropertyChanged抽象类实现的事件,并将其名称传递给事件
  • 在您的基类中,侦听PropertyChanged事件并IsDirty在触发时设置为true

最初创建抽象类有点工作,但它是一个更好的模型,用于监视数据更改,因为任何其他类都可以看到何时IsDirty(或任何其他属性)更改.

我的基类如下所示:

public abstract class BaseModel : INotifyPropertyChanged
{
    /// <summary>
    /// Initializes a new instance of the BaseModel class.
    /// </summary>
    protected BaseModel()
    {
    }

    /// <summary>
    /// Fired when a property in this class changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Triggers the property changed event for a specific property.
    /// </summary>
    /// <param name="propertyName">The name of the property that has changed.</param>
    public void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

任何其他模型然后只是扩展BaseModel,并NotifyPropertyChanged在每个setter中调用.