Click or drag to resize

Understanding and configuring collection change tracking

TrackerDog can also seamlessly track collection properties (1-n, m-n associations). Discover the quircks of collection change tracking on this document.

Index of contents
Under the hoods: how it works and how to support more collection types

Collection change handling is done implementing collection interfaces that work like interceptors.

Basically, the anatomy of an interceptor is:

C#
public sealed class CollectionChangeInterceptor : ICollection<T>
{
    public CollectionChangeInterceptor(CollectionChangeContext changeContext)
    {
        ChangeContext = changeContext;
    }

    private CollectionChangeContext ChangeContext { get; set; }

    // ICollection<T> member implementations like Add, Remove...here
}

Any collection change interceptor must provide a constructor with one parameter of type CollectionChangeContext.

CollectionChangeContext exposes some properties that not all interceptors might need, but it has three properties that are very important (see linked property documentation for further details):

When some collection method is called that may or may not change the collection, the collection change interceptor must add or remove items of AddedItems and RemovedItems properties to reflect which item(s) was/were added and/or removed. In addition, the whole called method must set Change property with a CollectionChange enumeration value to reflect if it has added or removed items from the collection, or otherwise if it produced no change in the collection.

For example, ICollectionTAdd(T) can be implemented as follows:

C#
public void Add(T item)
{
  // #1 Add the item to AddedItems set of ChangeContext
  ChangeContext.AddedItems.Add(item);
  // #2 Remove the item to RemovedItems to ensure that it won't be in both sides...
  ChangeContext.RemovedItems.Remove(item);
  // #3 Set that the collection change was adding items
  ChangeContext.Change = CollectionChange.Add;
}
Note Note

It's advisable that you check default collection change interceptor implementations provided by TrackerDog in order to correctly implement a new collection change interceptor:

How to configure TrackerDog to support collection change tracking

Actually, TrackerDog doesn't track any type of collection, but you must tell it to track certain collection types. TrackerDogConfiguration.CollectionConfiguration let's you customize which collection interfaces can be tracked on and what implementations should be instantiated and proxied to track their changes.

These are the default implementations provided out-of-the-box:

Right collection implementation is evaluated using FIFO (first-in, first-out) criteria. That is, projectName will look for the most specific implementation for a given collection type.

You can either add new collection interfaces or replace default implementations to already configured collection interfaces using the following method:

ICollectionChangeTrackingConfiguration must be accessed and configured getting it using the property Collections

Method arguments explained:

Note Note

Method arguments should be generic type definitions and you mustn't provide a collection interface and implementation giving collection item type:

  • WRONG:typeof(IList<User>), typeof(HashSet<User>)
  • OK:typeof(IList<>), typeof(HashSet<>)

When TrackerDog starts to track a collection property, it turns it into an instance of configured implementation to some collection interface and it turns the so-called property into a proxy that already implements INotifyCollectionChanged and implements on it the required members to track collection changes automatically.

The issue here is that, if your collection property is a different implementation of some configured collection interface, TrackerDog will be still able to proxy the whole collection, but it will instantiate the configured implementation. For that reason, in order to avoid further issues, it's advisable that collection properties should be typed using interface types:

C#
public ISet<User> Users { get; set; }

But if your collection property is typed using any custom ISetT implementation, it'll produce no issue because TrackerDog will detect that implements the whole interface and it will create a proxy based on its configured implementation.

If you want to let TrackerDog instantiate a proxy of a concrete collection interface, you'll add or replace an implementation:

C#
IObjectChangeTrackingConfiguration config = ObjectChangeTracking.CreateConfiguration();

// Why IList<string>, MyIListImplementation<string> and SomeIListInterceptor<string>?
// TrackerDog has opted-in to provide collection change tracking details as generic arguments to enforce some 
// rules during compile-time, and you can't provide generic arguments as generic type definitions. Hence, the collection 
// item type is just a dummy argument. You can provide int, object or whatever: TrackerDog won't use, it's just for the sake
// of being able to compile the code.
config.Collections.AddOrUpdateImplementation<IList<string>, MyIListImplementation<string>, SomeIListInterceptor<string>>();
Set collection notes

Most ISetT implementations require members to either override Equals(Object) and GetHashCode, or when using HashSetT some constructor overloads accept implementations of IEqualityComparerT implementations.

Current TrackerDog implementation will not be able to create a trackable collection of HashSetT providing the source untracked IEqualityComparerT. That is, set members will be forced to override both Equals(Object) and GetHashCode methods instead.

This limitation can be troublesome if member equality may vary depending on the context. Future versions of TrackerDog will try to address this limitation.

See Also