简体   繁体   English

我怎样才能钩住基类派生类的所有属性设置器?

[英]How can I hook into all property setters of derived classes from a base class?

I have a .net web application that for all intents and purposes of this question is CRUD with many different domain objects. 我有一个.net Web应用程序,对于该问题的所有意图和目的,都是带有许多不同域对象的CRUD。

A common theme across theses objects is the need to know which value properties have been modified as well as child domain model properties. 这些对象的共同主题是需要知道哪些值属性已被修改以及子域模型属性。 Currently we have two different systems in place for this. 当前,我们为此有两个不同的系统。

The value properties is the one I am trying to sort out with this question. 值属性是我正在尝试解决的问题。

Right now the models all inherit from the PersistableModel base that has these fields and methods of note: 现在,所有模型都继承自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);
     }
 }

Then within each individual model whenever a property is set we also need to call WasModified with a string of the property name and a boolean value. 然后,在每个单独的模型中,只要设置了属性,我们还需要使用属性名称和布尔值的字符串来调用WasModified。

Obviously this is very tedious and error prone, what I want to do is redesign this base class to automatically add entries to the dictionary when a derived class's property is set. 显然,这非常繁琐且容易出错,我想做的是重新设计此基类,以便在设置派生类的属性时自动将条目添加到字典中。

In my research the closest I've been able to get is to use PostSharp which is out of the question. 在我的研究中,最接近的方法是使用PostSharp,这是不可能的。

While working on a different project I came up with a solution that gets most of the way towards my original goal. 在进行不同的项目时,我想出了一个解决方案,该解决方案可以实现我最初的目标。

Note that this solution is reliant upon the Dev Express ViewModelBase as its base class, but it wouldn't be hard to make a new base class with the features being used for non Dev Express projects: 请注意,此解决方案依赖于Dev Express ViewModelBase作为其基类,但是使用非Dev Express项目中使用的功能来创建新的基类并不难:

Edit: I found an issue with the reset state logic, this update removes that issue. 编辑:我发现了重置状态逻辑的问题,此更新消除了该问题。

 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);
    }

Another quick note, the IsNewModel constructor flag serves to differentiate objects create on the UI level which don't need any kind of state tracking, and objects generated from the data access level which will need the state tracking. 另一个快速说明,IsNewModel构造函数标志用于区分在UI级别上创建的,不需要任何状态跟踪的对象,以及从数据访问级别生成的需要状态跟踪的对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM