Yesterday I was faced with a scenario that I have encountered many times before. I am developing a system in which I will be tracking things like articles and putting them into categories. I like my code to follow common patterns of abstraction and Domain design models. Users of the dataset get change tracking for free. Its time for the Domain Object community to get that as well.
The scenario: Domain object Article has a property called Categories that is a collection (think List<Category>). The persistence medium is a database that has 3 tables for articles, categories, and an article to category linking table. The UI must show all possible categories, indicate the ones the article is already in and handle changes by the user.
The shortcut: In the past my solution to this scenario was to simply remove the article from all categories and then add it back to the ones that were selected. That solution is very simple because you never have to worry about which categories the article was in before the update.
The ChangeAwareList<T>: As I pondered my previous shortcut solution to this problem I concluded that it was time to do it right. After all, some systems will have a problem if you are always recreating the article to category mappings. At a minimum the database probably has to re-index the data that may have already existed, but was just deleted and recreated. Also, the linking table may have other data in it that is being used elsewhere and the shortcut solution could cause problems in those scenarios. What is needed is a Domain class that can track the changes to a collection. Note, this is not for tracking changes within an object. The would be handled with a base class or an interface that tracks the state of an object. ChangeAwareList<T> is about tracking the state of a collection.
ChangeAwareList<T> inherits from List<T> and adds support for change tracking. It can be used anywhere a List<T> would be used. In my case I created a Categories class that simply inherits from ChangeAwareList<T>. public class Categories : ChangeAwareList<Category> {}
In my data access layer my Get operations load up the collection, turn on the change tracking, then hand it to the business layer. When the business layer hands back the collection it simply deletes all the items in the RemovedItems collection and adds all the items in the AddedItems collection. Anything that did not change is left alone. ChangeAwareList<T> allows you to work with the original collection, the new items, the deleted items, or the updated collection.
Here is the code for anyone interested.
public class ChangeAwareList<T> : List<T> { List<T> _addedItems; List<T> _removedItems; bool _trackChanges;
public ChangeAwareList() { _trackChanges = false; _addedItems = new List<T>(); _removedItems = new List<T>(); }
#region Clear public new void Clear() { if (_trackChanges) { _removedItems.AddRange(base.FindAll(AlwaysTrue)); } base.Clear(); } #endregion #region Remove public new bool Remove(T t) { if (_trackChanges) { _removedItems.Add(t); } return base.Remove(t); } public new void RemoveAt(int index) { if (_trackChanges) { _removedItems.Add(base[index]); } base.RemoveAt(index); }
public new void RemoveRange(int index, int count) { if (_trackChanges) { _removedItems.AddRange(base.GetRange(index,count)); } base.RemoveRange(index, count); } public new void RemoveAll(Predicate<T> match) { if (_trackChanges) { _removedItems.AddRange(base.FindAll(match)); } base.RemoveAll(match); } #endregion
#region Add Methods public new void Add(T t) { if (_trackChanges) { _addedItems.Add(t); } base.Add(t); } public new void AddRange(IEnumerable<T> collection) { if (_trackChanges) { _addedItems.AddRange(collection); } base.AddRange(collection); } #endregion
#region Insert public new void Insert(int index, T t) { if (_trackChanges) { _addedItems.Add(t); } base.Insert(index, t); }
public new void InsertRange(int index, IEnumerable<T> collection) { if (_trackChanges) { _addedItems.AddRange(collection); } base.InsertRange(index, collection); } #endregion #region Change Tracking public List<T> AddedItems { get { return _addedItems; } set { _addedItems = value; } }
public List<T> RemovedItems { get { return _removedItems; } set { _removedItems = value; } }
public List<T> OriginalItems { get { List<T> original = base.FindAll(AlwaysTrue);
foreach (T t in _addedItems) { original.Remove(t); } foreach (T t in _removedItems) { original.Add(t); } return original; } }
private bool AlwaysTrue(T t) { return true; }
public bool EnableChangeTracking { get { return _trackChanges; } set { _trackChanges = value; } } #endregion
}
Remember Me
Powered by: newtelligence dasBlog 1.7.5016.2
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.
© Copyright 2008, Cory Isakson
E-mail