EF Core 6 : AutoInclude(false) still loads Navigation

Viewed 31

READ THE EDIT!

I have two Entities :

public class Principal {
        public Guid Id { get; private set; }

        public Collection<Dependant> Dependants { get; init; } = new();

        public Principal() { }
}

public class Dependant{
        public Guid Id { get; private set; }

        public Guid PrincpalId { get; private set; }
        public Principal Principal{ get; private set; }

        public Dependant() { }
}

I access Principal through a repository :

internal class PrincipalsRepository {

    private readonly DbSet<Princpal> db;

    public PrincipalsRepository (DbSet<Princpal> db) {
        this.db = db;
    }

    public async Task AddAsync(Principal p) {
        await this.db.AddAsync(p).ConfigureAwait(false);
    }

    public async Task<Principal>> GetByIdAsync(Guid id) {
        //Notice how there's no Include here!
        return await db
            .FirstOrDefaultAsync(p => p.Id == id)
            .ConfigureAwait(false);
    }
}

I configure them like this :

    public void Configure(EntityTypeBuilder<Principal > builder) {

        builder
            .ToTable("Principals")
            .HasKey(p => p.Id);

        builder
            .Navigation(p => p.Dependants)
            .AutoInclude(false); //THIS!!!!!

        builder
            .OwnsMany(p =>
                p.Dependants,
                navBuilder => {
                    navBuilder.ToTable("Dependants");
                    navBuilder.Property<Guid>("Id"); //Important: without this EF would try to use 'int'
                    navBuilder.HasKey("Id");

                    navBuilder
                        .WithOwner(v => v.Principal)
                        .HasForeignKey(v => v.PrincipalId);

                }
        );

    }

The repo is used in a DbContext:

//PLEASE NOTE: This code might seem a bit broken to you because it's a trimmed down copy-paste from the real code.
public abstract class MyDatabase<TContext> : DbContext
    where TContext : DbContext {

    public PrincipalsRepository PrincipalsRepository = new PrincipalsRepository (DbPrincipals);

    //This is exposed for unit tests
    public DbSet<Principal> DbPrincipals { get; set; }

    public MyDatabase(DbContextOptions<TContext> options)
        : base(options) {
    }

}

I configure an in-memory Db :

//PLEASE NOTE: Not everything is detailed here. It's a copy paste from a bigger code base)
private static Database CreateDatabase() {

    var _connection = new SqliteConnection("Filename=:memory:");
    _connection.Open();

   
    _contextOptions = new DbContextOptionsBuilder<MyDatabase>()
        .UseSqlite(_connection)
        .Options;

    var context = new MyDatabase(_contextOptions);

    return context;
}

I run a unit test where I insert an Principal entity with a Dependant:

// Step 1 : Init
using var context = CreateDatabase();
var repo = new PrincipalsRepository(context.DbPrincipals);

// Step 2 : Insertion
var p = new Principal();
p.Dependants.Add(new Dependant());
await context.PrincipalsRepo.AddAsync(p).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false); 

// Step 3 : Read back
var p2 = context.PrincipalsRepo.GetByIdAsync(p.Id).ConfigureAwait(false);

And then...

Assert.Empty(p2!.Dependants); //The unit test fails because I can see that the Dependant has been loaded

What am I doing wrong? Why is it loaded despite me saying "AutoInclude(false)" ?

Note: After adding AutoInclude(false), creating a new migration changed the Db's model snapshot, but the migration itself was empty. Is that normal???

EDIT:

Like @DavidG and @Gert Arnold suggested in the comments, apparently I need to instantiate a brand new DbContext to do the test, because EF is somehow smart enough to pick up that p2 is the "same" as p, and... populates its navigation links (i.e. does the auto Include) without me asking?!? I absolutely don't understand what's the logic here (in terms of behaviour consistency). When I change the test and query p2 from a brand new DbContext instance, it works as I would expect it. I.e. it does find the Principal (p2) but its Dependants collection is empty.

Is this documented anywhere, in one form or another? Even as an implicit sentence that seems obvious on some Microsoft help page?

0 Answers
Related