EFCore DbContext showing different results despite having SavedChanges

c# ef-core-3.1 entity-framework-core

Question

I am struggling with EF Core in my MVC.NET Core project.

I have 2 classes that represent 2 tables in the database. An Owner and an Address, with a classic one to many relationship. In this situation, one owner can have many addresses but each address can only have 1 owner.

When I run the code below, no exceptions are thrown (other than the Assert).

var entity = new Entities();
entity.Database.EnsureDeleted();
entity.Database.EnsureCreated();

var owner = new Owner()
{
    Name = "Claire"
}

entity.Owner.Add(owner);
entity.SaveChanges();

var address = new Address()
{
    Street = "10 My Road",                
    Owner = owner //get the only owner from the database
};           

entity.Address.Add(address);
entity.SaveChanges();  //After save changes, if I check the `entity` object, I can see the `Owner` has a child of `Address` as expected.


//now re-test to make sure it's right
var ent = new Entities();
Assert.That(entity.Addresses.Count() == ent.Addresses.Count());  //This passes
Assert.That(entity.Owner.Single().Addresses.Count() == ent.Owner.Single().Addresses.Count()); //this fails

When I inspect the ent.Addresses object, I can see the Owner property is null. This is where the fault is but I don't understand why it's null.

My objects that represent the tables are very simple

[Table("Owner")]
internal class Owner
{
    public Owner()
    {
        Addresses = new HashSet<Address>();
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]

    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }

and

[Table("Address")]
internal class Address
{
    public string Street { get; set; }
    public virtual Owner Owner { get; set; }
}

And finally by DBContext class is

internal class Entities : DbContext
{
    public DbSet<Owner> Owners{ get; set; }

    public DbSet<Address> Addresses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var contextFactory = new ContextFactory();
        contextFactory.Configure(optionsBuilder);
    }
}

I only have 1 appsettings.json file with a single connection string in my application which is set to always copy to output directly!

A break point in the code where I recreate the database confirms it's only being recreated at start up!

My understanding is, that despite having 2 DBContext objects, everything should still work because I am saving the data to the database and then querying direct from the database.

Why is this happening?

1
0
4/13/2020 9:46:15 AM

Accepted Answer

The reason is explained in the Loading Related Data section of the EF Core documentation.

The first behavior is because EF Core currently does not support lazy loading, so normally you'll get null for navigation properties until you specifically load them via eager or explicit loading. However, the Eager loading section contains the following:

Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

which explains why the navigation property is not null in the second case.

Now, I'm not sure which of the two behaviors do you want to fix, so will try to address both.

The first behavior can be "fixed" by using one of the currently available methods for loading related data, for instance eager loading:

var mutants = db.Mutants.Include(m => m.OriginalCode).ToList();

The second behavior is "by design" and cannot be controlled. If you want to avoid it, make sure to use fresh new DbContext instance just for executing a single query to retry the data needed.

Update: Starting with v2.1, EF Core supports Lazy Loading. However it's not enabled by default, so in order to utilize it one should mark all navigation properties virtual, install Microsoft.EntityFrameworkCore.Proxies and enable it via UseLazyLoadingProxies call, or utilize Lazy-loading without proxies - both explained with examples in the EF Core documentation.

46
7/9/2018 6:02:40 PM


Related Questions





Related

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