0

I think it's fairly simple question but I am still unable to find any better solution for it. So after researching on this subject, I thought to ask this question here to have an expert opinion.

Basically, I am working on WPF application and I have defined GenericObserableCollection<T> implementing ObservableCollection<T> and most of the collections implement it to have a standard approach all over the project.

[Serializable]
[CollectionDataContract]
public class GenericObservableCollection<T> : ObservableCollection<T>
{
        public GenericObservableCollection() { }
        public GenericObservableCollection(IEnumerable<T> collection)
            : base(collection) { }
}

[Serializable]
[CollectionDataContract]
public class GenericRuleCollection : GenericObservableCollection<IRule>
{
        public GenericRuleCollection() { }
        public GenericRuleCollection(IEnumerable<IRule> collection) 
            : base(collection) { }
}

Initially, it was all good but later when Entity Framework came into the picture I had to change the domain design drastically because EF requires exposing ICollection<T> for mapping. At that point, I was confused keeping minimum changes and adapt to EF because I was new in it.

Later after researching, I came across few good articles handling this scenario.

I applied the same approach in my application domain creating ChildrenStorage to hold ICollection<GenericRule> as a requirement for EF.

Now I am looking for a smart and elegant approach to keep both of my collections, i.e. ChildrenStorage and Children collection in sync when items are added and/or removed. Since Children collection is the one that will get modified via UI so I want to keep track of any changes made to Children and wants to sync ChildrenStorage.

[Serializable]
[DataContract]
public abstract class GenericContainerRule : GenericRule
{
    protected GenericContainerRule() : this(null) 
    { 
        ChildrenStorage = new List<GenericRule>();
    }

    protected GenericContainerRule(string name) : base(name)
    {
        ChildrenStorage = new List<GenericRule>();
    }

    public void AddChild(IRule rule)
    {
        ChildrenStorage.Add(rule as GenericRule);
        _children = new GenericRuleCollection(ChildrenStorage);
        OnPropertyChanged(nameof(Children));
    }

    public class OrMappings
    {
        public static Expression<Func<GenericContainerRule, ICollection<GenericRule>>> ChildrenAccessor = t => t.ChildrenStorage;
    }

    [DataMember]
    protected ICollection<GenericRule> ChildrenStorage { get; set; }
    private GenericRuleCollection _children;
    public GenericRuleCollection Children => _children ?? (_children = new GenericRuleCollection(ChildrenStorage));

    private GenericRuleCollection _children;
    [DataMember]
    public virtual GenericRuleCollection Children
    {
        get { return _children; }
        private set { SetProperty(ref _children, value); }
    }
}
Furqan Safdar
  • 16,260
  • 13
  • 59
  • 93
  • 1
    But your GenericObservableCollection already implements ICollection, why you need to have another collection? – Evk Jun 28 '17 at 16:23
  • I don't need another collection, I just want that all changes to `ChildrenStorage` should be auto reflected in `Children` – Furqan Safdar Jun 28 '17 at 16:28
  • 1
    Ideally, you shouldn't be mixing your EntityFramework Data Model with your viewmodel (or even your "true" model depending on far you want to separate things). The Data Model is really meant purely for accessing data from the database, and won't be used when interacting with things like business rules. You would typically use some form of auto mapper to move data between the EF model and your business model. – Bradley Uffner Jun 28 '17 at 17:00
  • @BradleyUffner, thanks for your suggestion, I will consider separating Data Model from Domain Model at some later stage but at the moment my goal is to keep one collection for storage and other as wrapper collection. – Furqan Safdar Jun 28 '17 at 17:22
  • So storage collection should be List? Or which restrictions are there on its type? – Evk Jun 28 '17 at 18:30
  • I have added some more details to help understand my question. – Furqan Safdar Jun 28 '17 at 21:39

1 Answers1

0

I don't think I fully understand what you are asking, but as you talk about "change tracking collections" as a collection wrapper, this one may suit you. I'm currently using it for my final degree project. I put a link to it at the end.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace Core
{
    public interface IValidatableTrackingObject :
        IRevertibleChangeTracking, 
        INotifyPropertyChanged
    {
        bool IsValid { get; }
    }

    public class ChangeTrackingCollection<T> : ObservableCollection<T>, IValidatableTrackingObject
        where T : class, IValidatableTrackingObject
    {
        private IList<T> _originalCollection;

        private ObservableCollection<T> _addedItems;
        private ObservableCollection<T> _removedItems;
        private ObservableCollection<T> _modifiedItems;

        public ChangeTrackingCollection(IEnumerable<T> items)
            : base(items)
        {
            _originalCollection = this.ToList();

            AttachItemPropertyChangedHandler(_originalCollection);

            _addedItems = new ObservableCollection<T>();
            _removedItems = new ObservableCollection<T>();
            _modifiedItems = new ObservableCollection<T>();

            this.AddedItems = new ReadOnlyObservableCollection<T>(_addedItems);
            this.RemovedItems = new ReadOnlyObservableCollection<T>(_removedItems);
            this.ModifiedItems = new ReadOnlyObservableCollection<T>(_modifiedItems);
        }

        public ReadOnlyObservableCollection<T> AddedItems { get; private set; }
        public ReadOnlyObservableCollection<T> RemovedItems { get; private set; }
        public ReadOnlyObservableCollection<T> ModifiedItems { get; private set; }

        public bool IsChanged => AddedItems.Count > 0
                                || RemovedItems.Count > 0
                                || ModifiedItems.Count > 0;

        public bool IsValid => this.All(t => t.IsValid);

        public void AcceptChanges()
        {
            _addedItems.Clear();
            _removedItems.Clear();
            _modifiedItems.Clear();
            foreach (var item in this)
            {
                item.AcceptChanges();
            }

            _originalCollection = this.ToList();
            OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged)));
        }

        public void RejectChanges()
        {
            foreach (var removedItem in _removedItems.ToList()) this.Add(removedItem);
            foreach (var addedItem in _addedItems.ToList()) this.Remove(addedItem);
            foreach (var modifiedItem in _modifiedItems.ToList()) modifiedItem.RejectChanges();

            OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged)));
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var added = this.Where(current => _originalCollection.All(orig => orig != current));
            var removed = _originalCollection.Where(orig => this.All(current => current != orig));

            var modified = this.Except(added).Except(removed).Where(item => item.IsChanged).ToList();

            AttachItemPropertyChangedHandler(added);
            DetachItemPropertyChangedHandler(removed);

            UpdateObservableCollection(_addedItems, added);
            UpdateObservableCollection(_removedItems, removed);
            UpdateObservableCollection(_modifiedItems, modified);

            base.OnCollectionChanged(e);
            OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged)));
            OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsValid)));
        }

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(IsValid))
            {
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsValid)));
            }
            else
            {
                var item = (T)sender;
                if (_addedItems.Contains(item))
                {
                    return;
                }

                if (item.IsChanged)
                {
                    if (!_modifiedItems.Contains(item))
                    {
                        _modifiedItems.Add(item);
                    }
                }
                else
                {
                    if (_modifiedItems.Contains(item))
                    {
                        _modifiedItems.Remove(item);
                    }
                }

                OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChanged)));
            }
        }

        private void AttachItemPropertyChangedHandler(IEnumerable<T> items)
        {
            foreach (var item in items)
            {
                item.PropertyChanged += ItemPropertyChanged;
            }
        }

        private void DetachItemPropertyChangedHandler(IEnumerable<T> items)
        {
            foreach (var item in items)
            {
                item.PropertyChanged -= ItemPropertyChanged;
            }
        }

        private void UpdateObservableCollection(ObservableCollection<T> collection, IEnumerable<T> items)
        {
            collection.Clear();
            foreach (var item in items)
            {
                collection.Add(item);
            }
        }
    }
}

https://github.com/PFC-acl-amg/GamaPFC/blob/master/GamaPFC/Core/ChangeTrackingCollection.cs

  • 1
    Thanks but I have added some more details to my question. – Furqan Safdar Jun 28 '17 at 21:39
  • the provided link not working, after search use this instead : https://github.com/PFC-acl-amg/GamaPFC/blob/master/GamaPFC/Core/MVVM/ChangeTrackingCollection.cs – toumir Mar 01 '18 at 15:19