Entity Framework lazy loaded collection sometimes null

c# ef-code-first entity-framework entity-framework-6


I have two models, and one of them is the offspring of the other:

public class Parent
    [Key, Column("Parent")]
    public string Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Widget> Widgets { get; set; }

public class Widget
    public string Year { get; set; }

    public string ParentId { get; set; }

    public string Comments { get; set; }

    [Key, Column("ID_Widget")]
    public int Id { get; set; }

    [ForeignKey("ParentId"), JsonIgnore]
    public virtual Parent Parent { get; set; }

Over 99% of widgets are supported by this code:

var parent = _dbContext.Parents.FirstOrDefault(p => p.Id == parentId);

Usually, parent.Widgets is a collection that includes several items. However, in a few circumstances,parent.Widgets is empty (not a collection with no items).

Both the query for the parent and the query for widgets that belong to that parent were traced using Query Analyzer. Both give me precisely the rows I expected, however the model for one or two parent IDs gives the Widgets collection a null value. What may make some instances of a lazy-loaded collection null but not others?

8/2/2017 9:29:23 PM

Popular Answer

This problem often arises when a dbContext lifetime is kept open during an Add, a saveChanges operation, and a retrieve.

For instance:

var context = new MyDbContext(); // holding Parents.
var testParent = new Parent{Id = "Parent1", Name = "Parent 1"};

If you were to accomplish these things now:

var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1"); 

A parent wouldn't be given to you. Selection is based on state of commitment. So...

var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1"); 

Since the context is aware of this entity and has a reference to the object you constructed, this will return a reference to the parent you had added. The data state is not reached. In this instance, the Widgets collection will be #null since your definition for Widgets was just created using a get/set auto-property.

Upon doing so:

context = new MyDbContext();
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1"); 

In this instance, the parent is unknown to the new context, hence data state is used. You will get an empty list from EF rather than #null since there are no widgets to be loaded slowly.

It's preferable to stay away from auto-properties or initialise them in your function Object() { [native code] } when working with collection classes in EF to prevent this behaviour; you'll often want to assign Widgets after constructing a Parent. Because you don't want to promote ever utilising a setter on the collection property, initialising a default member is preferable.

For instance:

private readonly List<Widget> _widgets = new List<Widget>();
public virtual ICollection<Widget> Widgets
  get { return _widgets; }
  protected set { throw new InvalidOperationException("Do not set the Widget collection. Use Clear() and Add()"); }

Avoid setting a collection property since doing so will lead to issues with entity reference situations. If you wanted to organise your collection of Widgets by year, you could do something like:

parent.Widgets = parent.Widgets.OrderBy(x=> x.Year).ToList();

Seems innocent enough, but when you discovered that the Widgets reference was an EF proxy, you completely destroyed it. Change tracking on the collection can no longer be done by EF.

Initialize your collection to prevent unpleasant shocks while using references to #null collections. I'd also have a check at how long your dbContext has been active. While it's beneficial to maintain one initialised for the duration of a request or specific action, try to keep them alive no longer than is absolutely essential. Context change monitoring and similar activities use resources, and when they overlap operations, you may see apparently sporadic unusual behaviour like this.

8/4/2017 1:36:26 AM

Related Questions


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow