AddRange and AddRangeAsync of EF doesn't attach entities to the context

1.2k views Asked by At

I use the following code to insert a collection of entities into by DB using EntityFrameworkCore.SqlServer (7.0.7). I expect that my returned entities are set with an auto generated id which is a int identity(1,1) not null.

     public async Task<IEnumerable<Instrument>> CreateAsync(IEnumerable<Instrument> instruments)
    {         
        await  DbContext.AddRangeAsync(instruments);
 
        foreach (Instrument entity in instruments)
        {
            var entityEntry = DbContext.Entry(entity);
            Debug.WriteLine($"Entity State: {entityEntry.State}");
        }
        await DbContext.SaveChangesAsync();
        return instruments;
    }

the foreach loop has just been added to check the entity state which is detached . I tried also using DbContext.AddRange(instruments) instead of the async version and the entities are still detached and the id are not set. However if I iterate over my entity and call the following code on each indiviual entity, the is is set with a value:

     public async Task<Instrument> SaveAsync(Instrument entity)
    {
        if (entity.InstrumentId <= 0)
            await DbContext.AddAsync(entity);
        else
            DbContext.UpdateRange(entity);

        await DbContext.SaveChangesAsync();
        return entity;

    }

For what I know, calling AddRangeAsync should attach my entities to the context and set the value of my primary key for each entities. Has someone an idea or faced the same behaviour? Also is my code correct to add new entities into the db? Thanks for the help

1

There are 1 answers

2
Steve Py On

You will likely need to add some implementation details about how your DbContext is being configured and injected into where this "CreateAsync" call is, and the Instrument entity definition and any configuration. Such as any details like are you disabling change tracking or proxies or using explicit lazy-loading proxies?

I have run a test with AddRange and it worked as advertised. Following is the test code:

    [Test]
    public void TestAddRange()
    {
        using var context = new SoftDeleteDbContext();

        var parents = new Parent[]
        {
            new Parent{ Name = "AddRange1"},
            new Parent{Name ="AddRange2"}
        };
        foreach (var parent in parents)
        {
            var entry = context.Entry(parent);
            Debug.WriteLine($"{parent.ParentId} - {entry.State}");
        }
        context.AddRange(parents);
        foreach (var parent in parents)
        {
            var entry = context.Entry(parent);
            Debug.WriteLine($"{parent.ParentId} - {entry.State}");
        }
        context.SaveChanges();
        foreach (var parent in parents)
        {
            var entry = context.Entry(parent);
            Debug.WriteLine($"{parent.ParentId} - {entry.State}");
        }
    }

Don't mind the entity/DbContext names, this was just from an EF Core UnitTest project I use for testing out and asserting behaviour. The Parent entity uses an Identity column. The output from the first Debug Writelines before the AddRange was:

0 - Detached

0 - Detached

After the AddRange call:

0 - Added

0 - Added

And after the SaveChanges:

6 - Unchanged

7 - Unchanged

So, as mentioned & documented, the AddRange call is marking the items as "Added" while the IDs are set after SaveChanges is called. From the looks of things your AddRange/AddRangeAsync calls aren't actually doing anything against the DbContext which is certainly a curious behaviour.

Some things to check:

  1. Make sure nothing funky is happening with the way the DbContext is referenced in this class. I am assuming that the "DbContext" instance is an injected DbContext. Check that someone hasn't done something silly like:

    private AppDbContext DbContext => new AppDbContext();

... or something similar with a new instance or calling a DbContextFactory to create a new instance. This would have the AddRange and every other call operating against a different DbContext instance.

  1. Check that your application DbContext is not customized and overriding calls like AddRange and AddRangeAsync where they forgot to call base.AddRange(...) leaving the call neutered and ignored.

  2. Also, when testing this, are you running against an SQL Server instance or trying it out against something like an in-memory database? Is this a code-first or DB-first implementation, and have you confirmed that the Instrument table's ID is correctly set up as an Identity? There are certain limitations with in-memory databases so behavior for things like local tests and such can differ compared to runs against an actual database instance.

Hopefully that gives you some ideas to check, otherwise it would help to see the entity definition and any configuration which might shed some light on something possibly tripping it up.