我一直在乱用表达式 - 我可能超出了我的能力 - 但是这里......我实现了'类型安全'的INotifyPropertyChanged实现(例如here),但是走得更远,包括改变跟踪:
public abstract BaseViewModel<TEntity>:INotifyPropertyChanged
{
private readonly IBaseChangeTracker<TEntity> _changeTracker;
protected void OnPropertyChanged<T>(Expression<Func<T>> property, T value)
{
_changeTracker.AddChange(property, value);
OnPropertyChanged(property);
}
protected virtual void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(property.GetMemberInfo().Name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class BaseChangeTracker<TEntity>:IBaseChangeTracker<TEntity>
{
private readonly IDictionary<Expression, object> _changes = new Dictionary<Expression, object>();
public void AddChange<T>(Expression<Func<T>> expression, T newValue)
{
_changes.Add(expression, newValue);
}
public void ApplyChanges(TEntity entity)
{
foreach (var change in _changes)
{
var property = typeof(TEntity).GetProperty(change.Key.GetMemberInfo().Name);
property.SetValue(entity, change.Value, null);
}
}
public virtual void CopyCurrentState(TEntity entity)
{
_changes.Clear();
}
public virtual void ResetEntity(TEntity entity)
{
_changes.Clear();
}
public bool HasUnsavedChanges
{
get { return _changes.Any(); }
}
}
这可能看起来有点过分 - 每个实体都会拥有它自己的ChangeTracker在加载时保持实体的原始状态,并且可以将其重置回这些状态,但想法是如果在尝试时存在并发冲突保存更新的实体,我从数据库重新加载实体并通过.ApplyChanges运行它并尝试再次保存它。这将删除大约95%的并发问题......如果它有效。我的测试显示,对于有限的实体,它可以工作,但这是简单的属性更改。
已知问题:
我还缺少什么 - 或者我的设计中存在明显的缺陷?
答案 0 :(得分:1)
我意识到这不是您问题的直接答案,但您可以考虑使用命令模式来实现撤消/重做堆栈。
封装命令中的更改是循环/重新循环更改的一种非常整洁的方式,还有以下优点:(1)一个为应用程序增加价值的好功能,(2)您可以在任何给定的内容中包含许多操作命令,就像在do和undo方向上引发数据绑定支持的事件更改通知一样。
此外,管理集合更改并不比简单的属性更新更具挑战性。
具体到您发布的代码,OnPropertyChanged
实施永远不会引发PropertyChanged
事件,因为您在return
语句后调用if()
,然后再用裸括号调用({1}}这些与if
条件不对应。
if (PropertyChanged == null) return; // this returns based on if
{
return; // this returns no matter what
}
此外,似乎用户不会在UI中看到任何更改。在调用ApplyChanges
之前,这些值不会更新,并且在没有PropertyChanged
事件时也不会更新。 (我可能没有正确地遵循您的代码,但只是查看它似乎就是这种情况。)