Sunday 3 August 2014

Linq 2 SQL ObjectTrackingEnabled Memory Leaks

If you're working with disconnected entities with Linq 2 SQL you should understand that setting the ObjectTrackingEnabled flag on the data context is important to prevent memory leaks in regards to subscribed events as the default implied value for this flag is TRUE.

Take the following data access layer snippet for retrieving a Person entity (AdventureWorks db):
public Person GetPerson()
{
    using (AdventureWorksDataContext ctx = new AdventureWorksDataContext())
    {
        //ctx.ObjectTrackingEnabled = false;
        return ctx.Persons.First();
    }
}
If the ObjectTrackingEnabled flag is NOT set to FALSE on the data context then the lifetime of the implied change tracker object in Linq 2 SQL will remain alive as long as the entity's.  This is because the object change tracker in Linq 2 SQL subscribes to the PropertyChanged and PropertyChanging events on the entities which are materialised when the ObjectTrackingEnabled flag is set to TRUE.  This could become problematic when calling this method multiple times to retrieve numerous People within your application, because whenever the data context is instantiated a new instance of the change tracker is also created.  Therefore you'll have one change tracker for every entity!  However, once the entity goes out of scope for garbage collection the change tracker will also be garbage collected.

To prove this point the following code snippet shows two examples: the first, where the change tracker is kept alive by keeping the ObjectTrackingEnabled set to TRUE, and the second where the change tracker is discarded by setting the ObjectTrackingEnabled to FALSE.  The GetPerson method in the DataAccessLayer now contains some code where it retrieves the ChangeTracker object on the data context and assigns it to the WeakReference output parameter so the lifetime of the object can be tracked.
static void Main(string[] args)
{
    DataAccessLayer layer = new DataAccessLayer();

    WeakReference refCTWithTracking;
    Person person1 = layer.GetPerson(true, out refCTWithTracking);
    GC.Collect();
    Console.WriteLine("ChangeTrackerWithTracking IsAlive: {0}", refCTWithTracking.IsAlive);

    WeakReference refCTWithoutTracking;
    Person person2 = layer.GetPerson(false, out refCTWithoutTracking);
    GC.Collect();
    Console.WriteLine("ChangeTrackerWithoutTracking IsAlive: {0}", refCTWithoutTracking.IsAlive);

    Console.ReadKey();
}

public class DataAccessLayer
{
    public Person GetPerson(bool objectChangeTracking, out WeakReference refChangeTracker)
    {
        using (AdventureWorksDataContext ctx = new AdventureWorksDataContext())
        {
            ctx.ObjectTrackingEnabled = objectChangeTracking;

            PropertyInfo propServices = typeof(AdventureWorksDataContext).GetProperty("Services", BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo propChangeTracker = propServices.PropertyType.GetProperty("ChangeTracker", BindingFlags.Instance | BindingFlags.NonPublic);
            refChangeTracker = new WeakReference(propChangeTracker.GetValue(propServices.GetValue(ctx)));

            return ctx.Persons.First();
        }
    }
}
It would have been a better design for Microsoft to explicitly unsubscribe from the events on PropertyChanged and PropertyChanging when disposing of the data context as the lifetime of the change tracker should be scoped to that of the data context.  In my opinion, the change tracker is deemed redundant once the data context has been disposed.  Linq 2 SQL even prevents you reattaching a disconnected entity to another context if it has these events subscribed to by another change tracker.

No comments:

Post a Comment