2015-04-09 1 views
1

У меня есть веб-приложение .net, которое для всех целей и задач этого вопроса - 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, о котором не может быть и речи.

+0

Это проблема 'INotifyPropertyChagned' (каждое свойство автоматически активирует событие PropertyChanged при изменении), нет хорошего решения. То, как WPF обошел это, было через Dependancy Properties. Свойства, которые вы создаете, являются только оболочками для 'GetValue (' и 'SetValue (' методы на 'DependencyObject' –

+0

Я бы использовал защищенный индексатор в базовом классе, который использует частный словарь для сохранения значений. Таким образом, он может действовать как gatekeeper, где производные классы не реализуют свои собственные переменные-члены. – Biscuits

ответ

0

При работе над другим проектом я придумал решение, которое доставит большую часть пути к моей первоначальной цели.

Обратите внимание, что это решение зависит от Dev Экспресс ViewModelBase качестве базового класса, но это не было бы трудно сделать новый базовый класс с функциями используется для не проектов Dev Экспресс:

Редактировать : Я обнаружил проблему с логикой состояния сброса, это обновление устраняет эту проблему.

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 конструктор служит для различения объектов создать на уровне пользовательского интерфейса, которые не нуждаются в каком-либо государственная отслеживании, а также объекты, полученный от уровня доступа к данным, которые будут нуждаться отслеживание состояния ,

Смежные вопросы