skip to Main Content

In an ASP.Net 7 web api application, we have two sets of entities we need to build up with one set being built and persisted to the database, and the other set of entities being built in memory, but then discarded without persisting them so that we can preview the changes without saving. Both of these sets of entities are in the same database. We are using dependency injection to inject a DbContext into the controller when the controller is instantiated. In order to have separate "save contexts" such that one set of entities may be built without saving them while objects in the other "save contexts", I attempted to inject two instances of same DbContext into the controller.

public MyController(MyDbContext dbContext1, MyDbContext dbContext2)...

However, when I make changes to dbContext1 and the run dbContext1.SaveChanges(), I would expect that only the entities that I create and attach to dbContext1 are persisted, but what I have found is that the entities in dbContext2 are also persisted even though they are in a different instance of the DbContext.

public class MyController{
    MyDbContext _dbContext1;
    MyDbContext _dbContext2;

    public MyController(MyDbContext dbContext1, MyDBContext dbContext2){
        _dbContext1 = dbContext1;
        _dbContext2 = dbContext2;
    }

    [HttpPost]
    public async Task<IActionResult> AddAsync(MyEntity myEntity)
    {
        _dbContext1.MyEntities.Add(myEntity); // DO NOT want this entity saved

        _dbContext2.MyEntities.Add(new MyEntity()); // DO want this entity saved

        // Running this SaveChanges persists entities in both _dbContext1 and _dbContext2
        // which is NOT what we want. We want only _dbContext2 entities to be saved
        await _dbContext2.SaveChangesAsync(); 
    }
}

How do I maintain two separate sets of entities such that a save in one does not trigger a save in the other DbContext?

Can I still use dependency injection to do that, or do I have to manually manage the DbContexts?

Do I somehow use transactions within the same DbContext to manage different sets of entities?

2

Answers


  1. Both _dbContext1 and _dbContext2 are the same instance. EF’s default DbContext service-factory only creates a single DbContext instance per request-scope, and that instance is shared by all objects that use an injected DbContext in that same request scope.

    If you want multiple DbContexts you need to use IDbContextFactory<MyDbContext> – and you will also need to manage the lifetime of those DbContexts yourself (all you need is a using() block) as ASP.NET will not clean-up any DbContexts created via a factory.

    Change your code to this:

    public class MyController
    {
        private readonly IDbContextFactory<MyDbContext> dbFactory;
    
        public MyController( IDbContextFactory<MyDbContext> dbFactory )
        {
            this.dbFactory = dbFactory ?? throw new ArgumentNullException(nameof(dbFactory));
        }
    
        [HttpPost]
        public async Task<IActionResult> AddAsync(MyEntity myEntity, CancellationToken cancellationToken)
        {
            using( MyDbContext db1 = this.dbFactory.CreateDbContext() )
            using( MyDbContext db2 = this.dbFactory.CreateDbContext() )
            {
                db1.MyEntities.Add(myEntity);
                db2.MyEntities.Add(new MyEntity());
    
                await db1.SaveChangeAsync(cancellationToken);
                await db2.SaveChangeAsync(cancellationToken);
            }
        }
    }
    
    Login or Signup to reply.
  2. So without diving into WHY do you want to do this (I sense some XY Problem going on here) from pure technical standpoint of view one of the solutions would be using IDbContextFactory. To do that you will need to set it up in the DI:

    services.AddDbContextFactory<MyDbContext>(options => ...);
    

    And then inject into the controller:

    public class MyController{
        private readonly IDbContextFactory<MyDbContext> _dbFactory;
    
        public MyController(IDbContextFactory<MyDbContext> dbFactory){
            ...
        }
    
        [HttpPost]
        public async Task<IActionResult> AddAsync(MyEntity myEntity)
        {
            using MyDbContext db1 = this.dbFactory.CreateDbContext();
            using MyDbContext db2 = this.dbFactory.CreateDbContext();
            // ...
        }
    }
    

    Notes:

    1. Context created "manually" via factory should be disposed
    2. AddDbContextFactory allows resolving the context itself, so you can follow different patterns for different cases:
    public class MyController{
            private readonly MyDbContext _dbContext;
        private readonly IDbContextFactory<MyDbContext> _dbFactory;
    
        public MyController(
            MyDbContext dbContext, 
            IDbContextFactory<MyDbContext> dbFactory){
            // ...
        }
    
        [HttpPost]
        public async Task<IActionResult> AddAsync(MyEntity myEntity)
        {
            using MyDbContext tmpCtx = this.dbFactory.CreateDbContext();
            // use tmpCtx and _dbContext for different purposes 
        }
    }
    
    1. There are some other options (with some caveats depending on the application structure) – you can register the context as transient (by providing corresponding parameter for AddDbContext) or use change tracker to clear the changes (dbContext1.ChangeTracker.Clear();) but without understanding WHY it is hard to tell what would be the best one.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search