I am trying to maintain two copies of a list of the same type inside of my view model. One list is property of the base model the view model is bound to, and the other list is a property of the view model. My intention is for one of the copies to be the initially loaded list, so that at any point I can perform a "reset" that would update the model's list back to the original state. The problem is that every time I make an update to a property in the model's list, it also updates that property in the copy I am trying to keep as the original. It's acting like I'm creating a shadow copy but I am initializing it with "new" so I'm not sure why I'm not preserving the original state of the m_originalSubmissionQuestions. Thank you!
Model:
public class SubmissionModel : ModelBase
{
public int SubmissionId { get; set; }
private bool m_noBuildingPlacedInService;
public bool NoBuildingPlacedInService
{
get { return m_noBuildingPlacedInService; }
set
{
if (m_noBuildingPlacedInService == value)
return;
m_noBuildingPlacedInService = value;
RaisePropertyChanged();
}
}
private bool m_beginPeriodInFollowingYear;
public bool BeginPeriodInFollowingYear
{
get { return m_beginPeriodInFollowingYear; }
set
{
if (m_beginPeriodInFollowingYear == value)
return;
m_beginPeriodInFollowingYear = value;
RaisePropertyChanged();
}
}
public List<SubmissionSectionModel> Sections { get; } = new List<SubmissionSectionModel>();
}
Model:
public class SubmissionSectionModel : ModelBase
{
public bool ShowSectionHeader { get; set; }
public string SectionHeader { get; set; }
public List<SubmissionQuestionModel> Questions { get; } = new List<SubmissionQuestionModel>();
}
Model:
public class SubmissionQuestionModel : ModelBase
{
public int AnswerId { get; set; }
public int SubmissionFk { get; set; }
public short QuestionFk { get; set; }
public List<SubmissionAnswerModel> Answers { get; } = new List<SubmissionAnswerModel>();
private bool m_revisionRequired;
public bool RevisionRequired
{
get { return m_revisionRequired; }
set
{
if (m_revisionRequired == value)
return;
m_revisionRequired = value;
RaisePropertyChanged();
}
}
}
View Model:
public class SubmissionDetailsViewModel : ViewModelBase
{
private readonly IEventAggregator m_eventAggregator;
private readonly IAOCData m_aocData;
private readonly IUserData m_userData;
private ShowControlEvent m_showControlEvent;
private Guid m_submissionGuid;
private readonly int m_userId;
private bool? m_originalIsNoBuildingsInService = null;
private bool? m_originalIsOneBuildingInService = null;
public SubmissionDetailsViewModel (IEventAggregator eventAggregator, IAOCData aocData, IUserData userData)
{
m_eventAggregator = eventAggregator;
m_aocData = aocData;
m_userData = userData;
Setup_EventAggregator_EventHandlers();
CurrentViewState = Visibility.Collapsed;
}
private void Setup_EventAggregator_EventHandlers()
{
//event raisers
m_showControlEvent = new ShowControlEvent();
//event handlers
m_eventAggregator.EventHandler<ShowControlEvent>(e =>
{
if (e.ShowControlType == Enumerations.ShowControlTypes.AOCDetail)
{
if (!Guid.TryParse(e.Key, out Guid _id))
{
m_showControlEvent.ShowControlType = Enumerations.ShowControlTypes.AOCSearch;
m_eventAggregator.RaiseEvent(m_showControlEvent);
MessageBox.Show("Uknown submission id");
return;
}
//scroll to top and display
CurrentViewState = Visibility.Visible;
//data is already loaded - no reason to do it again
if (_id == m_submissionGuid)
{
return;
}
//clear all prior data
SubmissionModel = null;
DetailLoadingVisibilty = Visibility.Visible;
//set the ID
m_submissionGuid = _id;
//load the data
LoadDataAsync();
}
else if (e.ShowControlType == Enumerations.ShowControlTypes.AOCSearch)
{
CurrentViewState = Visibility.Collapsed;
}
});
}
private SubmissionModel m_submissionModel;
public SubmissionModel SubmissionModel
{
get => m_submissionModel;
set
{
if (m_submissionModel == value)
{
return;
}
m_submissionModel = value;
base.RaisePropertyChanged();
}
}
public void HandleInServiceChange()
{
AnnualOwnerCertificationSubmissionSectionModel _questionSection =
SubmissionModel.Sections.FirstOrDefault();
foreach (var _question in _questionSection.Questions)
{
_question.RevisionRequired = true;
}
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SubmissionModel.NoBuildingPlacedInService) || e.PropertyName == nameof(SubmissionModel.BeginPeriodInFollowingYear))
{
HandleInServiceChange();
}
}
private ObservableCollection<AnnualOwnerCertificationSubmissionQuestionModel> m_originalSubmissionQuestions;
public ObservableCollection<AnnualOwnerCertificationSubmissionQuestionModel> OriginalSubmissionQuestions
{
get { return m_originalSubmissionQuestions; }
set
{
if (m_originalSubmissionQuestions == value)
return;
m_originalSubmissionQuestions = value;
base.RaisePropertyChanged();
}
}
private async void LoadDataAsync()
{
//clear everything
SubmissionModel = null;
//start with getting the submission
SubmissionModel _submission;
_submission = await m_aocData.GetSubmission(m_submissionGuid);
SubmissionModel = _submission;
SubmissionModel.PropertyChanged += Model_PropertyChanged;
SubmissionSectionModel _questionSection = SubmissionModel.Sections.FirstOrDefault();
m_originalSubmissionQuestions = new ObservableCollection<SubmissionQuestionModel>(_questionSection.Questions);
DetailLoadingVisibilty = Visibility.Collapsed;
}
}
If I understand your question correctly, I suggest you add deep cloning methods.
For example, something like this:
public class SubmissionAnswerModel
{
// Some Code
public SubmissionAnswerModel DeepClone()
=> new SubmissionAnswerModel()
{
// Init properties and fields
};
}
public class SubmissionQuestionModel : ModelBase
{
public SubmissionQuestionModel DeepClone()
=> new SubmissionQuestionModel()
{
AnswerId = AnswerId,
SubmissionFk = SubmissionFk,
QuestionFk = QuestionFk,
Answers = new List<SubmissionAnswerModel>(Answers.Select(ans => ans.DeepClone()),
RevisionRequired = RevisionRequired
};
// Other Code
}
public class SubmissionSectionModel : ModelBase
{
public SubmissionSectionModel DeepClone()
=> new SubmissionSectionModel()
{
ShowSectionHeader = ,
SectionHeader = ,
Questions = new List<SubmissionQuestionModel> (Questions.Select(qst => qst.DeepClone))
}
// Other Code
}
private async void LoadDataAsync()
{
//clear everything
SubmissionModel = null;
//start with getting the submission
SubmissionModel _submission;
_submission = await m_aocData.GetSubmission(m_submissionGuid);
SubmissionModel = _submission;
SubmissionModel.PropertyChanged += Model_PropertyChanged;
SubmissionSectionModel _questionSection = SubmissionModel.Sections.FirstOrDefault(); // Original
SubmissionSectionModel _questionSectionClone = _questionSectionClone.DeepClone(); // Clone
m_originalSubmissionQuestions = new ObservableCollection<SubmissionQuestionModel>(_questionSectionClone.Questions);
DetailLoadingVisibilty = Visibility.Collapsed;
}
There are also many other options.
If it is possible to make large changes, then I would prefer to remove the properties from the SubmissionModel class and return the desired values using methods. In particular, the list should immediately be returned as a deep copy, not the original.
Also, the implementation of INotifyPropertyChanged in the model classes looks very questionable to me. I would prefer to make custom events in the SubmissionModel class, and make the rest of the classes immutable DTOs.