djskinner
djskinner

Reputation: 8125

Entity Framework 5: Entity is lazy loaded but the navigation properties are null when observing the Local collection

I'm experiencing what on first inspection appears to be a bug in Entity Framework 5.

I have a T4 generated DbContext and Entity classes. Note, I've modified the default T4 template a bit to support observable collections.

Lazy loading is enabled and is working fine throughout the application, except when I'm doing this:

courseEnrolment.Student.CourseEnrolments.ToList()

That is, the for the courseEnrolment I already have loaded in memory, I am accessing it's parent (Student) and loading all of the CourseEnrolments associated with it, which would also include the original courseEnrolment. When this happens, a second CourseEnrolment is successfully lazily loaded into the conext (and the Local collection) but all of it's navigation properties are null rather than being the corresponding DynamicProxy.

This is what the newly loaded CourseEnrolment looks like. Note that all the Navigation Properties are null despite the normal properties being successfully loaded from the database:

enter image description here

And this is what a normal CourseEnrolment looks like:

enter image description here

Does any one have any idea why an entity that was successfully lazily loaded is then unable to fulfil its own navigation properties by lazy loading?


UPDATE WITH MORE INFORMATION ABOUT THE SOURCE OF THE ISSUE

I've managed to recreate the issue with the following minimal code. The issue appears to be related to me observing the Local collection.

        var context = new PlannerEntities();

        Debug.Assert(context.CourseEnrolments.Local.Count() == 0);

        context.CourseEnrolments.Local.CollectionChanged += (sender, e) =>
        {
            Debug.Assert(e.NewItems.OfType<CourseEnrolment>().All(x => x.Adviser != null), "newly added entity has null navigatigon properties");
        };

        var c1 = context.CourseEnrolments.Single(x => x.EnrolmentId == "GA13108937");

        Debug.Assert(context.CourseEnrolments.Local.Count() == 1);
        Debug.Assert(context.CourseEnrolments.Local.All(x => x.Adviser != null));

        c1.Student.CourseEnrolments.ToList();

        Debug.Assert(context.CourseEnrolments.Local.Count() == 2);

        Debug.Assert(context.CourseEnrolments.Local.All(x => x.Adviser != null),"some navigation properties were not lazy loaded");

The assertion within the CollectionChanged handler fails which indicates at this point the navigation properties are not fulfilled. The final assertion does not fail, which would indicate at a later point, after the ObservableCollection events have been processed the entity is fulfilled.

Any ideas how I might access navigation properties on the CollectionChanged event of the Local collection?

Upvotes: 2

Views: 1473

Answers (2)

Jeow Li Huan
Jeow Li Huan

Reputation: 3796

The lazy-load proxies are not created until after the CollectionChanged event is called. Using Dispatcher causes the call to be placed on the message queue and executed some time later (how long later is not deterministic), thus the CollectionChanged event would have executed and the lazy-load proxies created, before the Debug.Assert gets called.

However, I would strongly avoid using Dispatcher for this purpose because it is non-deterministic and there is a risk of race-condition.

In order to use the CollectionChanged event like in your original code, you need change tracking proxy, not just the lazy-load proxy. For Entity Framework to generate change tracking proxies, ALL of your properties, be it of collection, object or primitive types, needs to be set as virtual.

Related issue: https://github.com/brockallen/BrockAllen.MembershipReboot/issues/290

Upvotes: 1

djskinner
djskinner

Reputation: 8125

Well I've managed to get it working by Dispatching the Assert call

context.CourseEnrolments.Local.CollectionChanged += (sender, e) =>
{
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>{
        Debug.Assert(e.NewItems.OfType<CourseEnrolment>().All(x => x.Adviser != null), "newly added entity has null navigation properties");
     }));
};

Is this the best solution?

Upvotes: 0

Related Questions