C# 将类属性标记为脏

发布于 2024-07-18 11:13:09 字数 997 浏览 7 评论 0原文

下面是一个简单的枚举示例,它定义了对象的状态,而类则显示了该枚举的实现。

public enum StatusEnum
{
    Clean = 0,
    Dirty = 1,
    New = 2,
    Deleted = 3,
    Purged = 4
}


public class Example_Class
{
    private StatusEnum _Status = StatusEnum.New;

    private long _ID;
    private string _Name;

    public StatusEnum Status
    {
        get { return _Status; }
        set { _Status = value; }
    }

    public long ID
    {
        get { return _ID; }
        set { _ID = value; }
    }

    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }
}

当使用数据库中的数据填充类对象时,我们将枚举值设置为“clean”。 为了将大部分逻辑保留在表示层之外,我们如何在属性更改时将枚举值设置为“脏”。

我在想一些类似的事情;

public string Name
{
    get { return _Name; }
    set 
    {
        if (value != _Name)
        {
               _Name = value; 
           _Status = StatusEnum.Dirty;
        }
    }   
}

在类的每个属性的设置器中。

这听起来是个好主意吗?有没有人对如何分配脏标志有更好的想法,而无需在表示层中这样做。

The following is a simple example of an enum which defines the state of an object and a class which shows the implementation of this enum.

public enum StatusEnum
{
    Clean = 0,
    Dirty = 1,
    New = 2,
    Deleted = 3,
    Purged = 4
}


public class Example_Class
{
    private StatusEnum _Status = StatusEnum.New;

    private long _ID;
    private string _Name;

    public StatusEnum Status
    {
        get { return _Status; }
        set { _Status = value; }
    }

    public long ID
    {
        get { return _ID; }
        set { _ID = value; }
    }

    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }
}

when populating the class object with data from the database, we set the enum value to "clean". with the goal of keeping most of the logic out of the presentation layer, how can we set the enum value to "dirty" when a property is changed.

i was thinking something along the lines of;

public string Name
{
    get { return _Name; }
    set 
    {
        if (value != _Name)
        {
               _Name = value; 
           _Status = StatusEnum.Dirty;
        }
    }   
}

in the setter of each property of the class.

does this sound like a good idea, does anyone have any better ideas on how the dirty flag can be assigned without doing so in the presentation layer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

忆梦 2024-07-25 11:13:09

当您确实想要在类级别(或者就此而言,通知)使用脏标志时 - 您可以使用如下技巧来最大程度地减少属性中的混乱(此处显示 IsDirtyPropertyChanged,只是为了好玩)。

显然,使用枚举方法是一件微不足道的事情(我没有使用枚举方法的唯一原因是为了保持示例简单):

class SomeType : INotifyPropertyChanged {
    private int foo;
    public int Foo {
        get { return foo; }
        set { SetField(ref foo, value, "Foo"); }
    }

    private string bar;
    public string Bar {
        get { return bar; }
        set { SetField(ref bar, value, "Bar"); }
    }

    public bool IsDirty { get; private set; }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void SetField<T>(ref T field, T value, string propertyName) {
        if (!EqualityComparer<T>.Default.Equals(field, value)) {
            field = value;
            IsDirty = true;
            OnPropertyChanged(propertyName);
        }
    }
    protected virtual void OnPropertyChanged(string propertyName) {
        var handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

您也可以选择将其中一些推入抽象基类中,但这是一个单独的讨论

When you really do want a dirty flag at the class level (or, for that matter, notifications) - you can use tricks like below to minimise the clutter in your properties (here showing both IsDirty and PropertyChanged, just for fun).

Obviously it is a trivial matter to use the enum approach (the only reason I didn't was to keep the example simple):

class SomeType : INotifyPropertyChanged {
    private int foo;
    public int Foo {
        get { return foo; }
        set { SetField(ref foo, value, "Foo"); }
    }

    private string bar;
    public string Bar {
        get { return bar; }
        set { SetField(ref bar, value, "Bar"); }
    }

    public bool IsDirty { get; private set; }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void SetField<T>(ref T field, T value, string propertyName) {
        if (!EqualityComparer<T>.Default.Equals(field, value)) {
            field = value;
            IsDirty = true;
            OnPropertyChanged(propertyName);
        }
    }
    protected virtual void OnPropertyChanged(string propertyName) {
        var handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

You might also choose to push some of that into an abstract base class, but that is a separate discussion

感受沵的脚步 2024-07-25 11:13:09

一种选择是在写入时更改它; 另一种方法是保留所有原始值的副本,并在有人要求时计算脏度。 这还有一个额外的好处,您可以准确地知道哪些字段已更改(以及以何种方式更改),这意味着您可以发出最少的更新语句并使合并冲突解决稍微容易一些。

您还可以将所有脏污检查放在一处,这样就不会污染代码的其余部分。

我并不是说它是完美的,但这是一个值得考虑的选择。

One option is to change it on write; another is to keep a copy of all the original values and compute the dirtiness when anyone asks for it. That has the added benefit that you can tell exactly which fields have changed (and in what way) which means you can issue minimal update statements and make merge conflict resolution slightly easier.

You also get to put all the dirtiness-checking in one place, so it doesn't pollute the rest of your code.

I'm not saying it's perfect, but it's an option worth considering.

懒的傷心 2024-07-25 11:13:09

如果你想以这种方式实现它,并且你想减少代码量,你可以考虑应用面向方面编程。

例如,您可以使用像 PostSharp 这样的编译时编织器,并创建一个可应用于特性。 这方面可以确保在适当的时候设置脏标志。

该方面可以如下所示:

[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class ChangeTrackingAttribute : OnMethodInvocationAspect
{
    public override void OnInvocation( MethodInvocationEventArgs e )
    {
        if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) )
        {
              // we're in the setter
              IChangeTrackable target = e.Delegate.Target as IChangeTrackable;

              // Implement some logic to retrieve the current value of 
              // the property
              if( currentValue != e.GetArgumentArray()[0] )
              {
                  target.Status = Status.Dirty;
              }
              base.OnInvocation (e);
        } 
    }  
} 

当然,这意味着您想要实现 ChangeTracking 的类应该实现 IChangeTrackable 接口(自定义接口),该接口至少具有“Status”属性。

您还可以创建自定义属性 ChangeTrackingProperty,并确保上面创建的方面仅应用于用此 ChangeTrackingProperty 属性修饰的属性。

例如:

public class Customer : IChangeTrackable
{
    public DirtyState Status
    {
        get; set;
    }

    [ChangeTrackingProperty]
    public string Name
    { get; set; }
}

这就是我的一点看法。
您甚至可以确保 PostSharp 在编译时检查具有用 ChangeTrackingProperty 属性修饰的属性的类是否实现 IChangeTrackable 接口。

If you want to implement it in this way, and you want to reduce the amount of code, you might consider applying Aspect Oriented Programming.

You can for instance use a compile-time weaver like PostSharp , and create an 'aspect' that can be applied to properties. This aspect then makes sure that your dirty flag is set when appropriate.

The aspect can look like this:

[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class ChangeTrackingAttribute : OnMethodInvocationAspect
{
    public override void OnInvocation( MethodInvocationEventArgs e )
    {
        if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) )
        {
              // we're in the setter
              IChangeTrackable target = e.Delegate.Target as IChangeTrackable;

              // Implement some logic to retrieve the current value of 
              // the property
              if( currentValue != e.GetArgumentArray()[0] )
              {
                  target.Status = Status.Dirty;
              }
              base.OnInvocation (e);
        } 
    }  
} 

Offcourse, this means that the classes for which you want to implement ChangeTracking, should implement the IChangeTrackable interface (custom interface), which has at least the 'Status' property.

You can also create a custom attribute ChangeTrackingProperty, and make sure that the aspect that has been created above, is only applied to properties that are decorated with this ChangeTrackingProperty attribute.

For instance:

public class Customer : IChangeTrackable
{
    public DirtyState Status
    {
        get; set;
    }

    [ChangeTrackingProperty]
    public string Name
    { get; set; }
}

This is a little bit how I see it.
You can even make sure that PostSharp checks at compile-time whether classes that have properties that are decorated with the ChangeTrackingProperty attribute, implement the IChangeTrackable interface.

不打扰别人 2024-07-25 11:13:09

该方法基于该线程中提供的一组不同概念。 我想我会把它发布给任何正在寻找一种干净有效的方法的人,就像我自己一样。

这种混合概念的关键在于:

  1. 您不想重复数据,以避免数据膨胀和资源占用;
  2. 您想知道对象的属性何时从给定的原始/干净状态发生变化;
  3. 您希望 IsDirty 标志既准确又需要很少的处理时间/功率来返回值; 并且
  4. 您希望能够告诉对象何时再次认为自己是干净的。 这在 UI 中构建/工作时特别有用。

考虑到这些要求,这就是我想到的,它似乎非常适合我,并且在处理 UI 和准确捕获用户更改时变得非常有用。 我还在下面发布了“如何使用”,向您展示我如何在 UI 中使用它。

对象

public class MySmartObject
{
    public string Name { get; set; }
    public int Number { get; set; }
    private int clean_hashcode { get; set; }
    public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } }

    public MySmartObject()
    {
        this.Name = "";
        this.Number = -1;
        MakeMeClean();

    }

    public MySmartObject(string name, int number)
    {
        this.Name = name;
        this.Number = number;
        MakeMeClean();
    }

    public void MakeMeClean()
    {
        this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode();
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode() ^ this.Number.GetHashCode();
    }
}

它非常简单,满足了我们的所有要求:

  1. 对于脏检查,数据不会重复...
  2. 这考虑了所有属性更改场景(请参阅下面的场景)...
  3. 当您调用IsDirty 属性,执行一个非常简单且小的 Equals 操作,并且可以通过 GetHashCode 重写完全自定义...
  4. 通过调用 MakeMeClean 方法,您现在再次拥有一个干净的对象!

当然,您可以对其进行调整以涵盖许多不同的状态……这完全取决于您。 此示例仅显示如何进行正确的 IsDirty 标志操作。

场景
让我们回顾一下一些场景,看看会返回什么结果:

  • 场景 1
    使用空构造函数创建新对象,
    属性名称从“”更改为“James”,
    调用 IsDirty 返回 True! 准确。

  • 场景 2
    使用参数“John”和 12345 创建新对象,
    属性名称从“John”更改为“James”,
    属性名称从“James”更改回“John”,
    调用 IsDirty 返回 False。 准确,而且我们也不需要复制数据来做到这一点!

如何使用,WinForms UI 示例
这只是一个示例,您可以通过 UI 以多种不同的方式使用它。

假设您有两种表单([A] 和 [B])。

第一个 ([A]) 是主窗体,第二个 ([B]) 是允许用户更改 MySmartObject 内的值的窗体。

[A] 和 [B] 表单都声明了以下属性:

public MySmartObject UserKey { get; set; }

当用户单击 [A] 表单上的按钮时,将创建 [B] 表单的实例,设置其属性并显示为一个对话框。

表单 [B] 返回后,[A] 表单根据 [B] 表单的 IsDirty 检查更新其属性。 像这样:

private void btn_Expand_Click(object sender, EventArgs e)
{
    SmartForm form = new SmartForm();
    form.UserKey = this.UserKey;
    if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty)
    {
        this.UserKey = form.UserKey;
        //now that we have saved the "new" version, mark it as clean!
        this.UserKey.MakeMeClean();
    }
}

另外,在 [B] 中,当关闭时,您可以检查并提示用户是否正在关闭其中包含未保存更改的表单,如下所示:

    private void BForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        //If the user is closing the form via another means than the OK button, or the Cancel button (e.g.: Top-Right-X, Alt+F4, etc).
        if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore)
        {
            //check if dirty first... 
            if (this.UserKey.IsDirty)
            {
                if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
                    e.Cancel = true;
            }

        }

    }

正如您从上面的示例中看到的,这可以是非常有用的东西,因为它确实简化了用户界面。

注意事项

  • 每次实现此功能时,您都必须根据您正在使用的对象对其进行自定义。 例如:如果不使用反射,就没有“简单”的通用方法来做到这一点……如果使用反射,就会降低效率,尤其是在大型和复杂的对象中。

希望这对某人有帮助。

This method is based on a set of different concepts provided in this thread. I thought i'd put it out there for anyone that is looking for a way to do this cleanly and efficiently, as i was myself.

The key of this hybrid concept is that:

  1. You don't want to duplicate the data to avoid bloating and resource hogging;
  2. You want to know when the object's properties have changed from a given original/clean state;
  3. You want to have the IsDirty flag be both accurate, and require little processing time/power to return the value; and
  4. You want to be able to tell the object when to consider itself clean again. This is especially useful when building/working within the UI.

Given those requirements, this is what i came up with, and it seems to be working perfectly for me, and has become very useful when working against UIs and capturing user changes accurately. I have also posted an "How to use" below to show you how I use this in the UI.

The Object

public class MySmartObject
{
    public string Name { get; set; }
    public int Number { get; set; }
    private int clean_hashcode { get; set; }
    public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } }

    public MySmartObject()
    {
        this.Name = "";
        this.Number = -1;
        MakeMeClean();

    }

    public MySmartObject(string name, int number)
    {
        this.Name = name;
        this.Number = number;
        MakeMeClean();
    }

    public void MakeMeClean()
    {
        this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode();
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode() ^ this.Number.GetHashCode();
    }
}

It's simple enough and addresses all of our requirements:

  1. The data is NOT duplicated for the dirty check...
  2. This takes into account all property changes scenarios (see scenarios below)...
  3. When you call the IsDirty property, a very simple and small Equals operation is performed and it is fully customizable via the GetHashCode override...
  4. By calling the MakeMeClean method, you now have a clean object again!

Of course you can adapt this to encompass a bunch of different states... it's really up to you. This example only shows how to have a proper IsDirty flag operation.

Scenarios
Let's go over some scenarios for this and see what comes back:

  • Scenario 1
    New object is created using empty constructor,
    Property Name changes from "" to "James",
    call to IsDirty returns True! Accurate.

  • Scenario 2
    New object is created using paramters of "John" and 12345,
    Property Name changes from "John" to "James",
    Property Name changes back from "James" to "John",
    Call to IsDirty returns False. Accurate, and we didn't have to duplicate the data to do it either!

How to use, a WinForms UI example
This is only an example, you can use this in many different ways from a UI.

Let's say you have a two forms ([A] and [B]).

The first([A]) is your main form, and the second([B]) is a form that allows the user to change the values within the MySmartObject.

Both the [A] and the [B] form have the following property declared:

public MySmartObject UserKey { get; set; }

When the user clicks a button on the [A] form, an instance of the [B] form is created, its property is set and it is displayed as a dialog.

After form [B] returns, the [A] form updates its property based on the [B] form's IsDirty check. Like this:

private void btn_Expand_Click(object sender, EventArgs e)
{
    SmartForm form = new SmartForm();
    form.UserKey = this.UserKey;
    if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty)
    {
        this.UserKey = form.UserKey;
        //now that we have saved the "new" version, mark it as clean!
        this.UserKey.MakeMeClean();
    }
}

Also, in [B], when it is closing, you can check and prompt the user if they are closing the form with unsaved changes in it, like so:

    private void BForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        //If the user is closing the form via another means than the OK button, or the Cancel button (e.g.: Top-Right-X, Alt+F4, etc).
        if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore)
        {
            //check if dirty first... 
            if (this.UserKey.IsDirty)
            {
                if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
                    e.Cancel = true;
            }

        }

    }

As you can see from the examples above, this can be a very useful thing to have since it really streamlines the UI.

Caveats

  • Every time you implement this, you have to customize it to the object you're using. E.g.: there's no "easy" generic way of doing this without using reflection... and if you use reflection, you lose efficiency, especially in large and complex objects.

Hopefully this helps someone.

岁月流歌 2024-07-25 11:13:09

看一下 PostSharp (http://www.postsharp.org/)。
您可以轻松创建一个将其标记为脏的属性,您可以将属性添加到需要它的每个属性,并将所有代码保留在一个位置。

粗略地说,创建一个接口,让该类实现它。
创建一个可应用于属性并转换为界面的属性,以便在标记属性之一发生更改时设置值。

Take a look at PostSharp (http://www.postsharp.org/).
You can easily create a Attribute which marks it as dirty you can add the attrubute to each property that needs it and it keeps all your code in one place.

Roughly speaking Create an interface which has your status in make the class implement it.
Create an attribute which can be applied on properties and cast to your interface in order to set the value when something changes one of the marked properties.

终难愈 2024-07-25 11:13:09

你的方法基本上就是我会做的。 我只想
删除 Status 属性的设置器:

public StatusEnum Status
{
    get { return _Status; }
    // set { _Status = value; }
}

并添加一个函数

public SetStatusClean()
{
    _Status = StatusEnum.Clean;
}

以及 SetStatusDeleted()SetStatusPurged(),因为我发现它更好地表明了意图。

编辑

阅读乔恩·斯基特的回答< /a>,我需要重新考虑我的方法;-) 对于简单的对象,我会坚持我的方式,但如果它变得更复杂,他的建议将导致更好的组织代码。

Your approach is basically how I would do it. I would just
remove the setter for the Status property:

public StatusEnum Status
{
    get { return _Status; }
    // set { _Status = value; }
}

and instead add a function

public SetStatusClean()
{
    _Status = StatusEnum.Clean;
}

As well as SetStatusDeleted() and SetStatusPurged(), because I find it better indicates the intention.

Edit

Having read the answer by Jon Skeet, I need to reconsider my approach ;-) For simple objects I would stick with my way, but if it gets more complex, his proposal would lead to much better organised code.

你列表最软的妹 2024-07-25 11:13:09

如果您的 Example_Class 是轻量级的,请考虑存储原始状态,然后将当前状态与原始状态进行比较,以确定更改。 如果不是,您的方法是最好的,因为在这种情况下存储原始状态会消耗大量系统资源。

If your Example_Class is lightweight, consider storing the original state and then comparing the current state to the original in order to determine the changes. If not your approach is the best because stroing the original state consumes a lot of system resources in this case.

昔梦 2024-07-25 11:13:09

除了“考虑让你的类型不可变”的建议之外,这是我写的一些东西(并且让 Jon 和 Marc 一路教我一些东西)

public class Example_Class
{    // snip
     // all properties are public get and private set

     private Dictionary<string, Delegate> m_PropertySetterMap;

     public Example_Class()
     {
        m_PropertySetterMap = new Dictionary<string, Delegate>();
        InitializeSettableProperties();
     }
     public Example_Class(long id, string name):this()
     {   this.ID = id;    this.Name = name;   }

     private void InitializeSettableProperties()
     {
        AddToPropertyMap<long>("ID",  value => { this.ID = value; });
        AddToPropertyMap<string>("Name", value => { this.Name = value; }); 
     }
     // jump thru a hoop because it won't let me cast an anonymous method to an Action<T>/Delegate
     private void AddToPropertyMap<T>(string sPropertyName, Action<T> setterAction)
     {   m_PropertySetterMap.Add(sPropertyName, setterAction);            }

     public void SetProperty<T>(string propertyName, T value)
     {
        (m_PropertySetterMap[propertyName] as Action<T>).Invoke(value);
        this.Status = StatusEnum.Dirty;
     }
  }

你明白了..可能的改进:使用 PropertyNames 和 常量 检查属性是否真的发生了变化。
这里的一个缺点是

obj.SetProperty("ID", 700);         // will blow up int instead of long
obj.SetProperty<long>("ID", 700);   // be explicit or use 700L

Apart from the advice of 'consider making your type immutable', here's something I wrote up (and got Jon and Marc to teach me something along the way)

public class Example_Class
{    // snip
     // all properties are public get and private set

     private Dictionary<string, Delegate> m_PropertySetterMap;

     public Example_Class()
     {
        m_PropertySetterMap = new Dictionary<string, Delegate>();
        InitializeSettableProperties();
     }
     public Example_Class(long id, string name):this()
     {   this.ID = id;    this.Name = name;   }

     private void InitializeSettableProperties()
     {
        AddToPropertyMap<long>("ID",  value => { this.ID = value; });
        AddToPropertyMap<string>("Name", value => { this.Name = value; }); 
     }
     // jump thru a hoop because it won't let me cast an anonymous method to an Action<T>/Delegate
     private void AddToPropertyMap<T>(string sPropertyName, Action<T> setterAction)
     {   m_PropertySetterMap.Add(sPropertyName, setterAction);            }

     public void SetProperty<T>(string propertyName, T value)
     {
        (m_PropertySetterMap[propertyName] as Action<T>).Invoke(value);
        this.Status = StatusEnum.Dirty;
     }
  }

You get the idea.. possible improvements: Use constants for PropertyNames & check if property has really changed.
One drawback here is that

obj.SetProperty("ID", 700);         // will blow up int instead of long
obj.SetProperty<long>("ID", 700);   // be explicit or use 700L
や莫失莫忘 2024-07-25 11:13:09

我是这样做的。

如果我不需要测试特定字段是否脏,
我有一个抽象类:

public abstract class SmartWrap : ISmartWrap
{
    private int orig_hashcode { get; set; }
    private bool _isInterimDirty;

    public bool IsDirty
    {
        get { return !(this.orig_hashcode == this.GetClassHashCode()); }
        set
        {
            if (value)
                this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode();
            else
                MakeClean();
        }
    }

    public void MakeClean()
    {
        this.orig_hashcode = GetClassHashCode();
        this._isInterimDirty = false;
    }

    // must be overridden to return combined hashcodes of fields testing for
    // example Field1.GetHashCode() ^ Field2.GetHashCode() 
    protected abstract int GetClassHashCode();

    public bool IsInterimDirty
    {
        get { return _isInterimDirty; }
    }

    public void SetIterimDirtyState()
    {
        _isInterimDirty = this.IsDirty;
    }

    public void MakeCleanIfInterimClean()
    {
        if (!IsInterimDirty)
            MakeClean();
    }

    /// <summary>
    /// Must be overridden with whatever valid tests are needed to make sure required field values are present.
    /// </summary>
    public abstract bool IsValid { get; }
}

}

以及一个接口

public interface ISmartWrap
{
    bool IsDirty { get; set; }
    void MakeClean();
    bool IsInterimDirty { get;  }
    void SetIterimDirtyState();
    void MakeCleanIfInterimClean();
}

这允许我进行部分保存,并在有其他详细信息需要保存时保留 IsDirty 状态。 并不完美,但涵盖了很多内容。

临时 IsDirty 状态的使用示例(为了清楚起见,删除了错误包装和验证):

            area.SetIterimDirtyState();

            if (!UpdateClaimAndStatus(area))
                return false;

            area.MakeCleanIfInterimClean();

            return true;

这对于大多数情况都很好,但是对于某些类,我想使用原始数据的支持字段来测试每个字段,并且返回更改列表或者至少更改了字段的枚举。
随着字段枚举的更改,我可以将其通过消息链向上推送,以便有选择地更新远程缓存中的字段。

Here is how i do it.

In cases where i do not need to test for specific fields being dirty,
I have an abstract class:

public abstract class SmartWrap : ISmartWrap
{
    private int orig_hashcode { get; set; }
    private bool _isInterimDirty;

    public bool IsDirty
    {
        get { return !(this.orig_hashcode == this.GetClassHashCode()); }
        set
        {
            if (value)
                this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode();
            else
                MakeClean();
        }
    }

    public void MakeClean()
    {
        this.orig_hashcode = GetClassHashCode();
        this._isInterimDirty = false;
    }

    // must be overridden to return combined hashcodes of fields testing for
    // example Field1.GetHashCode() ^ Field2.GetHashCode() 
    protected abstract int GetClassHashCode();

    public bool IsInterimDirty
    {
        get { return _isInterimDirty; }
    }

    public void SetIterimDirtyState()
    {
        _isInterimDirty = this.IsDirty;
    }

    public void MakeCleanIfInterimClean()
    {
        if (!IsInterimDirty)
            MakeClean();
    }

    /// <summary>
    /// Must be overridden with whatever valid tests are needed to make sure required field values are present.
    /// </summary>
    public abstract bool IsValid { get; }
}

}

As well as an interface

public interface ISmartWrap
{
    bool IsDirty { get; set; }
    void MakeClean();
    bool IsInterimDirty { get;  }
    void SetIterimDirtyState();
    void MakeCleanIfInterimClean();
}

This allows me to do partial saves, and preserve the IsDirty state if there is other details to save. Not perfect, but covers a lot of ground.

Example of usage with interim IsDirty State (Error wrapping and validation removed for clarity):

            area.SetIterimDirtyState();

            if (!UpdateClaimAndStatus(area))
                return false;

            area.MakeCleanIfInterimClean();

            return true;

This is good for most scenarios, however for some classes i want to test for each field with a backing field of original data, and either return a list of changes or at least an enum of fields changed.
With an enum of fields changed i can then push that up through a message chain for selective update of fields in remote caches.

_失温 2024-07-25 11:13:09

您还可以考虑对变量进行装箱,这会降低性能,但也有其优点。 它非常简洁,您不会在不设置脏状态的情况下意外更改值。

public class Variable<T>
{
    private T _value;
    private readonly Action<T> _onValueChangedCallback;

    public Variable(Action<T> onValueChangedCallback, T value = default)
    {
        _value = value;
        _onValueChangedCallback = onValueChangedCallback;
    }

    public void SetValue(T value)
    {
        if (!EqualityComparer<T>.Default.Equals(_value, value))
        {
            _value = value;
            _onValueChangedCallback?.Invoke(value);
        }
    }

    public T GetValue()
    {
        return _value;
    }

    public static implicit operator T(Variable<T> variable)
    {
        return variable.GetValue();
    }
}

然后挂钩一个回调,将您的类标记为脏。

public class Example_Class
{
    private StatusEnum _Status = StatusEnum.New;

    private Variable<long> _ID;
    private Variable<string> _Name;

    public StatusEnum Status
    {
        get { return _Status; }
        set { _Status = value; }
    }

    public long ID => _ID;
    public string Name => _Name;

    public Example_Class()
    {
         _ID = new Variable<long>(l => Status = StatusEnum.Dirty);
         _Name = new Variable<string>(s => Status = StatusEnum.Dirty);
    }
}

You could also think about boxing your variables, which comes at a performance cost, but also has its merits. It is pretty consise and you cannot accidentally change a value without setting your dirty status.

public class Variable<T>
{
    private T _value;
    private readonly Action<T> _onValueChangedCallback;

    public Variable(Action<T> onValueChangedCallback, T value = default)
    {
        _value = value;
        _onValueChangedCallback = onValueChangedCallback;
    }

    public void SetValue(T value)
    {
        if (!EqualityComparer<T>.Default.Equals(_value, value))
        {
            _value = value;
            _onValueChangedCallback?.Invoke(value);
        }
    }

    public T GetValue()
    {
        return _value;
    }

    public static implicit operator T(Variable<T> variable)
    {
        return variable.GetValue();
    }
}

and then hook in a callback that marks your class as dirty.

public class Example_Class
{
    private StatusEnum _Status = StatusEnum.New;

    private Variable<long> _ID;
    private Variable<string> _Name;

    public StatusEnum Status
    {
        get { return _Status; }
        set { _Status = value; }
    }

    public long ID => _ID;
    public string Name => _Name;

    public Example_Class()
    {
         _ID = new Variable<long>(l => Status = StatusEnum.Dirty);
         _Name = new Variable<string>(s => Status = StatusEnum.Dirty);
    }
}
小清晰的声音 2024-07-25 11:13:09

另一种方法是重写 GetHashCode() 方法,如下所示:

public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function...
{
    var sb = new System.Text.StringBuilder();

    sb.Append(_dateOfBirth);
    sb.Append(_marital);
    sb.Append(_gender);
    sb.Append(_notes);
    sb.Append(_firstName);
    sb.Append(_lastName);  

    return sb.ToString.GetHashCode();
}

从数据库加载后,获取对象的哈希码。 然后在保存之前检查当前哈希码是否等于先前的哈希码。 如果它们相同,则不保存。

编辑:

正如人们指出的那样,这会导致哈希代码发生变化 - 当我使用 Guid 来识别我的对象时,我不介意哈希代码是否发生变化。

Edit2:

由于人们反对更改哈希码,因此不要重写 GetHashCode 方法,只需将该方法调用为其他名称即可。 重点是检测变化,而不是使用 guid 还是哈希码进行对象识别。

Another method is to override the GetHashCode() method to somthing like this:

public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function...
{
    var sb = new System.Text.StringBuilder();

    sb.Append(_dateOfBirth);
    sb.Append(_marital);
    sb.Append(_gender);
    sb.Append(_notes);
    sb.Append(_firstName);
    sb.Append(_lastName);  

    return sb.ToString.GetHashCode();
}

Once loaded from the database, get the hash code of the object. Then just before you save check if the current hash code is equal to the previous hash code. if they are the same, don't save.

Edit:

As people have pointed out this causes the hash code to change - as i use Guids to identify my objects, i don't mind if the hashcode changes.

Edit2:

Since people are adverse to changing the hash code, instead of overriding the GetHashCode method, just call the method something else. The point is detecting a change not whether i use guids or hashcodes for object identification.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文