CR_eeper
CR_eeper

Reputation: 65

Implementing IsDirty to properties on my Model (WPF MVVM)

I have an observable collection of Suppliers that I want to load into a gridview and then have users edit any relevant information on the supplier. My issue is I'm not sure how to implement an IsDirty field for each property on the supplier (Model) that can be changed. I have the IsDirty bits created as such

    #region SupplierID
    private int _SupplierID;
    public int SupplierID
    {
        get
        {
            return _SupplierID;
        }
        set
        {
            if (_SupplierID != value)
            {
                _SupplierID = value;
                OnPropertyChanged("SupplierID");
            }
        }
    }
    #endregion

    #region Address
    private string _Address;
    public string Address
    {
        get
        {
            return _Address;
        }
        set
        {
            if (_Address != value)
            {
                _Address = value;
                IsDirtyAddress = true;
                OnPropertyChanged("Address");
            }
        }
    }

    public bool IsDirtyAddress{ get; set; }
    #endregion

    #region City
    private string _City;
    public string City
    {
        get
        {
            return _City;
        }
        set
        {
            if (_City != value)
            {
                _City = value;
                IsDirtyCity = true;
                OnPropertyChanged("City");
            }
        }
    }

    public bool IsDirtyCity { get; set; }
    #endregion

    #region State
    private string _State;
    public string State
    {
        get
        {
            return _State;
        }
        set
        {
            if (_State != value)
            {
                _State = value;
                IsDirtyState = true;
                OnPropertyChanged("State");
            }
        }
    }

    public bool IsDirtyState { get; set; }
    #endregion

    #region Zip
    private string _Zip;
    public string Zip
    {
        get
        {
            return _Zip;
        }
        set
        {
            if (_Zip != value)
            {
                _Zip = value;
                IsDirtyZip = true;
                OnPropertyChanged("Zip");
            }
        }
    }

    public bool IsDirtyZip { get; set; }
    #endregion

The problem is that when I build the list of suppliers (ViewModel), I actually end up setting the IsDirty bits to true. What is the best way to set my Address, City, State, Zip when creating the supplier without setting the IsDirty bits to true. Do I need an initialization function in my Model?

    for (int i = 0; i < dtSupplier.Rows.Count; i++)
    {
        Supplier s = new Supplier()
        {
            SupplierID = Convert.ToInt32(dtSupplier.Rows[i]["SupplierID"].ToString()),
            Address = dtSupplier.Rows[i]["Address"].ToString(),
            City = dtSupplier.Rows[i]["City"].ToString(),
            State = dtSupplier.Rows[i]["State"].ToString(),
            Zip = dtSupplier.Rows[i]["Zip"].ToString()
        };

        Suppliers.Add(s);
    }

Maybe I'm going about the whole IsDirty approach the wrong way. I just want to know which values actually changed so my SQL update statement will only include the changed values when a user saves. Thanks!

Upvotes: 1

Views: 1516

Answers (2)

CodingYoshi
CodingYoshi

Reputation: 27019

You need to do a few things:

  1. Add a flag to your ViewModel and name it Loading. When you are loading the ViewModel, set the Loading property to true. When finished loading, set it to false.
  2. Pass your model to your ViewModel but do not expose it. Simply store it in your ViewModel.
  3. When the property is set, check if the ViewModel is in state Loading and do not set IsDirty flags. Also, even if not in loading state, compare the values to the value in your model and see if they are the same.
  4. Do not use hardcoded strings because it is easy to make a mistake. Use nameof (see my example below).
  5. Do not let other people from outside set the IsDirty flag so make the setter private.
  6. I am pretty sure there are libraries that do this already but I do not know of any so perhaps someone else can chime in.

Here is a hypothetical example:

public class Model
{
    public string Name { get; set; }
}

public class ViewModel : INotifyPropertyChanged
{
    private readonly Model model;

    public ViewModel(Model model)
    {
        this.model = model;
    }

    public bool Loading { get; set; }

    public bool IsDirtyName { get; private set; }
    private string name;
    public string Name
    {
        get
        {
            return this.name;
        }
        set
        {
            if (this.Loading)
            {
                this.name = value;
                return;
            }

            if (this.model.Name != value)
            {
                IsDirtyName = true;
                OnPropertyChanged(nameof(Name));
            }
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        // ...
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

If you pay attention the above design, you do not even need all those IsDirty flags and IsLoading etc. You can actually just have one method in your ViewModel that you call during saving and ask it to check and return all the properties that have changed. The ViewModel will compare its own properties against the Model properties and return a dictionary. There are many ways do achieve what you want.

Upvotes: 1

Jan Paolo Go
Jan Paolo Go

Reputation: 6522

One option is to handle the IsDirty logic on a different class which will store the original values of the Supplier object instance. You can then use that class to GetChangedPropertyNames or check if your object HasChanges.

class Supplier
{
    private string _Address;
    public string Address
    {
        get
        {
            return _Address;
        }
        set
        {
            if (_Address != value)
            {
                _Address = value;
                OnPropertyChanged("Address");
            }
        }
    }
}

class SupplierIsDirtyTracker
{
    private Dictionary<string, object> _originalPropertyValues = new Dictionary<string, object>();
    private Supplier _supplier;
    public void Track(Supplier supplier)
    {
        _supplier = supplier;
        _originalPropertyValues.Add(nameof(Supplier.Address), supplier.Address);
    }

    public bool HasChanges()
    {
        return !Equals(_originalPropertyValues[nameof(Supplier.Address)], _supplier.Address);
    }

    public IEnumerable<string> GetChangedPropertyNames()
    {
        if(!Equals(_originalPropertyValues[nameof(Supplier.Address)], _supplier.Address))
        {
            yield return nameof(Supplier.Address);
        }
    }
}

You can also use Reflection on your IsDirtyTracker class to eliminate hardcoding the property names.

Upvotes: 0

Related Questions