2
votes

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?

2
Have you tried to use Include while loading the second CourseEnrolments? e.g CourseEnrolments.Students.Include("CourseEnrolments") - idipous
Can you confirm whether Student.CourseEnrolments is marked virtual? - Colin
@idipous Include is not a method on these collections since they are of type ObservableCollection. The only place I could use Include would be on the DbSet of the context i.e. entities.Students.Include('CourseEnrolment').ToList(); - djskinner
@Colin yes, in the Student class, the CourseEnrolments property is defined as public virtual ObservableCollection<CourseEnrolment> CourseEnrolments - djskinner
Does it work if you change it to public virtual ICollection<CourseEnrolment> CourseEnrolments ? - Colin

2 Answers

1
votes

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

0
votes

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?