如何从基类挂钩到派生类的所有属性设置器?

时间:2015-04-09 20:08:57

标签: c# inheritance aop

我有一个.net Web应用程序,对于这个问题的所有意图和目的,CRUD都有许多不同的域对象。

跨这些对象的共同主题是需要知道哪些值属性已被修改以及子域模型属性。目前,我们有两个不同的系统。

价值属性是我试图用这个问题解决的问题。

现在模型都继承自PersistableModel基础,该基础具有以下字段和方法:

 private readonly List<string> _modifiedProperties = new List<string>();
 public virtual ModelState State { get; set; }
 public IEnumerable<string> ModifiedProperties { get { return _modifiedProperties; } }
 protected bool HasModifiedProperties { get { return 0 < _modifiedProperties.Count; } }
 public bool WasModified(string propertyName)
 {
      return _modifiedProperties.Contains(propertyName);
 }
 public void WasModified(string propertyName, bool modified)
 {
     if (modified)
     {
         if (!WasModified(propertyName)) _modifiedProperties.Add(propertyName);
     }
     else 
     {
         _modifiedProperties.Remove(propertyName);
     }
 }

然后在每个单独的模型中,只要设置了属性,我们还需要使用属性名称和布尔值的字符串调用WasModified。

显然这非常繁琐且容易出错,我想要做的是重新设计这个基类,以便在设置派生类的属性时自动向词典添加条目。

在我的研究中,我能得到的最接近的是使用PostSharp,这是不可能的。

1 个答案:

答案 0 :(得分:0)

在处理不同的项目时,我想出了一个解决方案,它可以实现我最初的目标。

请注意,此解决方案依赖于Dev Express ViewModelBase作为其基类,但使用非Dev Express项目所使用的功能创建新的基类并不困难:

编辑:我发现重置状态逻辑存在问题,此更新消除了该问题。

 public abstract class UpdateableModel : ViewModelBase
{
    private static readonly MethodInfo GetPropertyMethod;
    private static readonly MethodInfo SetPropertyMethod;

    private readonly bool _trackingEnabled;
    private readonly Dictionary<string, Tuple<Expression, object>> _originalValues;
    private readonly List<string> _differingFields;

    static UpdateableModel() 
    {
        GetPropertyMethod = typeof(UpdateableModel).GetMethod("GetProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
        SetPropertyMethod = typeof(UpdateableModel).GetMethod("SetProperty");
    }

    protected UpdateableModel(bool isNewModel)
    {
        _originalValues = new Dictionary<string, Tuple<Expression, object>>();
        _differingFields = new List<string>();
        if (isNewModel) return;

        State = ModelState.Unmodified;
        _trackingEnabled = true;
    }

    public ModelState State
    {
        get { return GetProperty(() => State); }
        set { base.SetProperty(() => State, value); }
    }

    public new bool SetProperty<T>(Expression<Func<T>> expression, T value)
    {
        var wasUpdated = base.SetProperty(expression, value);
        if (_trackingEnabled && wasUpdated)
        {
            UpdateState(expression, value);
        }
        return wasUpdated;
    }

    /// <summary>
    /// Reset State is meant to be called when discarding changes, it will reset the State value to Unmodified and set all modified values back to their original value.
    /// </summary>
    public void ResetState()
    {
        if (!_trackingEnabled) return;
        foreach (var differingField in _differingFields)
        {
            var type = GetFuncType(_originalValues[differingField].Item1);

            var genericPropertySetter = SetPropertyMethod.MakeGenericMethod(type);
            genericPropertySetter.Invoke(this, new[]{_originalValues[differingField].Item2});
        }
    }

    /// <summary>
    /// Update State is meant to be called after changes have been persisted, it will reset the State value to Unmodified and update the original values to the new values.
    /// </summary>
    public void UpdateState()
    {
        if (!_trackingEnabled) return;
        foreach (var differingField in _differingFields)
        {
            var type = GetFuncType(_originalValues[differingField].Item1);
            var genericPropertySetter = GetPropertyMethod.MakeGenericMethod(type);
            var value = genericPropertySetter.Invoke(this, new object[] { _originalValues[differingField].Item1 });

            var newValue = new Tuple<Expression, object>(_originalValues[differingField].Item1,value);
            _originalValues[differingField] = newValue;
        }

        _differingFields.Clear();
        State = ModelState.Unmodified;
    }

    private static Type GetFuncType(Expression expr)
    {
        var lambda = expr as LambdaExpression;
        if (lambda == null)
        {
            return null;
        }
        var member = lambda.Body as MemberExpression;
        return member != null ? member.Type : null;
    }

    private void UpdateState<T>(Expression<Func<T>> expression, T value)
    {
        var propertyName = GetPropertyName(expression);
        if (!_originalValues.ContainsKey(propertyName))
        {
            _originalValues.Add(propertyName, new Tuple<Expression,object>(expression, value));
        }

        else
        {
            if (!Compare(_originalValues[propertyName].Item2, value))
            {
                _differingFields.Add(propertyName);
            }
            else if (_differingFields.Contains(propertyName))
            {
                _differingFields.Remove(propertyName);                   
            }

            State = _differingFields.Count == 0 
                ? ModelState.Unmodified 
                : ModelState.Modified;
        }
    }

    private static bool Compare<T>(T x, T y)
    {
        return EqualityComparer<T>.Default.Equals(x, y);
    }

另一个快速说明,IsNewModel构造函数标志用于区分在UI级别上创建的对象,这些对象不需要任何类型的状态跟踪,以及从数据访问级别生成的对象,这些对象将需要状态跟踪。