EF Core 5.0.4 - Eager Loading via Include() does not work after upgrade from core 3.1

Viewed 907

we recently migrated to .net core 5.0.4 from 3.1 and EF eager loading has stopped functioning. It no longer loads the dependent object.

We attempt to load a dependent entity like so (but related Permissions obj is not loaded):

var test = _db.Guardians.Include(x => x.Permissions).First(x => x.Id == id);

Although the PermissionId Fk is populated, the dependent entity is not: enter image description here

Other than migrating to EF Core 5, there have been no other changes.

POCO in case it helps:

public class GuardianModel : PersonModel
{
    public int PermissionsId { get; set; }
    [ForeignKey("PermissionsId")]
    public virtual GuardianPermission Permissions { get; set; }

    public GuardianModel() : base()
    {
        Permissions = new GuardianPermission();
        Active = true;
    }
}
public class GuardianPermission
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public bool MessageAsEmail { get; set; }
    public bool Permission { get; set; }
    public bool Message { get; set; }
    public bool CovidReportEmail { get; set; }
    public bool PermissionEmails { get; set; }
    public bool System { get; set; }
    public GuardianPermission()
    {
        MessageAsEmail = true;
        Permission = true;
        Message = true;
        PermissionEmails = true;
    }
}

I have tried every answer google returned, added explicit FK reference, FK is no longer nullable, removed virtual keyword, nothing made a difference.

Any guidance/troubleshooting steps is greatly appreciated.

2 Answers

Ok, so I post my comment as an answer:

Breaking changes in EF Core 5.0
"Non-null reference navigations are not overwritten by queries"

https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes#nonnullreferences

I.e. if navigation property is not null - it will be not populated by EF (except empty collections).

From docs:

Old behavior
In EF Core 3.1, reference navigations eagerly initialized to non-null values would sometimes be overwritten by entity instances from the database, regardless of whether or not key values matched. However, in other cases, EF Core 3.1 would do the opposite and leave the existing non-null value.

New behavior
Starting with EF Core 5.0, non-null reference navigations are never overwritten by instances returned from a query.

Note that eager initialization of a collection navigation to an empty collection is still supported.

As per @apocalypse's suggestion, it is related to breaking changes as per https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes#nonnullreferences :

Non-null reference navigations are not overwritten by queries Tracking Issue #2693

Old behavior In EF Core 3.1, reference navigations eagerly initialized to non-null values would sometimes be overwritten by entity instances from the database, regardless of whether or not key values matched. However, in other cases, EF Core 3.1 would do the opposite and leave the existing non-null value.

New behavior Starting with EF Core 5.0, non-null reference navigations are never overwritten by instances returned from a query.

Note that eager initialization of a collection navigation to an empty collection is still supported.

Why Initialization of a reference navigation property to an "empty" entity instance results in an ambiguous state. For example:

C#

public class Blog
{
    public int Id { get; set; }
    public Author Author { get; set; ) = new Author();
}

Normally a query for Blogs and Authors will first create Blog instances and then set the appropriate Author instances based on the data returned from the database. However, in this case every Blog.Author property is already initialized to an empty Author. Except EF Core has no way to know that this instance is "empty". So overwriting this instance could potentially silently throw away a valid Author. Therefore, EF Core 5.0 now consistently does not overwrite a navigation that is already initialized.

This new behavior also aligns with the behavior of EF6 in most cases, although upon investigation we also found some cases of inconsistency in EF6.

Mitigations If this break is encountered, then the fix is to stop eagerly initializing reference navigation properties.

In my case:

  public GuardianModel() : base()
{
    Permissions = **new GuardianPermission();**
    Active = true;
}

The offending line is bolded above. My solution was to remove the default initialization and ensure the dependent class is instantiated after the primary class has been instantiated. Is there a better way. ie:

var g = new GuardianModel();
g.Permissions = new GuardianPermission();

There must be a better way than having to locate all above instances.

Related