One does not simply use just one DbContext with multiple ConnectionStrings in Entity Framework Core.

How to implement multiple Connection Strings for one DbContext in EF Core?

This post was most recently updated on March 28th, 2023.

4 min read.

Recently, while building an app service to host a .NET Core API, I had to implement the logic for using both Read-Only and Read-Write Database Contexts for Entity Framework Core. In this particular case, it was the same database – just different contexts, because depending on the location of the app service the app was deployed in, read and write operations might actually go to different database instances,

That’s really easy, right?

Well… Yes and no. Essentially, it’s easy to spin up but comes with some caveats.

Time needed: 25 minutes

Essentially, you just need to have 2 Database Context classes inheriting your primary Context (in this case, ReadOnlyApplicationDbContext and ReadWriteApplicationDbContext are inheriting ApplicationDbContext)

  1. using System;
    using System.Linq;
    ...
     
    namespace YourNamespace
    {
        public class ApplicationDbContext : IdentityDbContext
        {
           
            protected ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
                : base(options)
            {
            }
     
            protected ApplicationDbContext(DbContextOptions options)
                : base(options)
            {
            }
     
            public DbSet<YourStuff> YourStuff { get; set; }
        }
     
        public class ReadWriteApplicationDbContext : ApplicationDbContext
        {
            internal ReadWriteApplicationDbContext(DbContextOptions options)
                : base(options)
            {
            }
     
            public ReadWriteApplicationDbContext(DbContextOptions<ReadWriteApplicationDbContext> options)
                : base(options)
            {
            }
        }
     
        public class ReadOnlyApplicationDbContext : ApplicationDbContext
        {
            internal ReadOnlyApplicationDbContext(DbContextOptions options)
                : base(options)
            {
     
            }
     
            public ReadOnlyApplicationDbContext(DbContextOptions<ReadOnlyApplicationDbContext> options)
                : base(options)
            {
            }
     
            // <summary>
            // This context cannot be used for saving. In production, this configuration is automatic (it connects to a read-only db), but in localhost it needs to be underlined like this.
            // </summary>
            public override int SaveChanges()
            {
                throw new Exception("This is a read-only context!");
            }
        }
    }


    In this case, I’m using just one model, and one database, but two contexts – so I’m inheriting both of my contexts from my pre-existing database context (ApplicationDbContext) that actually has the entities in it.

  2. Then, just inject two separate contexts to your application on startup – something like this in your Startup.cs file:

    namespace YourApp
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
     
            public IConfiguration Configuration { get; }
     
            public void ConfigureServices(IServiceCollection services)
                  {           
                                 // Add the read-only connection as the default
                                services.AddDbContext<ReadOnlyApplicationDbContext>(options =>
                                 {
                                 options.UseSqlServer(Configuration.GetConnectionString("ReadOnlyConnection"));
                  });
                                              
                                 // And add the read-write connection to be used if need be
                                services.AddDbContext<ReadWriteApplicationDbContext>(options =>
                  {
                     options.UseSqlServer(Configuration.GetConnectionString("ReadWriteConnection"));
                                 });
     
                                 // Rest of the implementation omitted for clarity ...
                  }
        }
    }

  3. After creating the database context classes and configuring them in Startup, you’re ready to actually use them! Inject both of the contexts in your code.

    Below, I’m injecting both the ReadOnly and Read-Write -database contexts into the same controller.

    Injecting 2 DbContexts into the constructor of a Controller class in .NET Core / EF Core.
    Injecting 2 DbContexts into the constructor of a Controller class in .NET Core / EF Core.

    And then use them like… Well, like you’d use any DbContext, I suppose!

  4. In case you want to initiate it in an Azure Function, it looks something like the below:


    How to initiate an Entity Framework Core Database Context in an Azure Function?
    How to initiate an Entity Framework Core Database Context in an Azure Function?

Since my example code overrides the SaveChanges() for the read-only context, you can actually simulate the different connection strings even when developing locally (without going through the hassle of generating a kind-of-a-read-only connection string for your local MS SQL Express database).

And obviously, if you’re using actual connection strings, just be mindful not to mix them up, and you should be good!

Caveats

Ok, so this is not without side effects. What kind of a hacky workaround article would it otherwise be, right? :)

I’ve got the list of caveats or other things to note down here:

Don’t inject your parent database context into the same project with the read-only database context

I started by configuring my original database context (ApplicationDbContext) and creating a separate read-only database context that was just extending it and overriding SaveChanges().

However, this wouldn’t work. No matter what I did, the 2 DbContexts ended up using the same connection string!

I turned off any caching options I could find, and switched one of the contexts to use pooling and the other to not, but no – it turned out to be pretty impossible.

Code-first migrations will break

Well, it’ll break and it won’t. You’ll need to choose between a couple of options.

So, in case you have your entities configured with code-first migrations, you need to either still have a project where you use the “original”, parent ApplicationDbContext that’ll let you either run the migrations during startup (something like the code below), using PowerShell (Update-Database -context ApplicationDbContext with a proper project selected as default) or change the associated DbContext from all of the migrations.

using (var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>())
{
   context.Database.Migrate();
}

I wasn’t brave enough to change the DbContext for the migrations – even the thought was pretty frightening – so instead I opted to leave the ApplicationDbContext in use in one of my projects (it’s a big solution). But in case you want to try your hand in changing the migrations, the picture below illustrates what you need to change:

[DbContext(typeof(ApplicationDbContext))] 

// needs to be replaced with something like:
[DbContext(typeof(ReadWriteApplicationDbContext))]
The "Migrations" folder in a project with Entity Framework Core code-first migrations has a bunch of files that'll take care of the creation of your model - but they're tied to a certain database context, as illustrated by the [DbContext(typeof(ApplicationDbContext))] attribute.
The “Migrations” folder in a project with Entity Framework Core code-first migrations has a bunch of files that’ll take care of the creation of your model – but they’re tied to a certain database context, as illustrated by the [DbContext(typeof(ApplicationDbContext))] attribute.

One more alternative option would be to inject it during ConfigureServices, and the migration should happen just as nicely.

Oh – this is why I didn’t make ApplicationDbContext abstract, in case you were wondering! ;)

References

mm
4.2 10 votes
Article Rating
Subscribe
Notify of
guest

2 Comments
most voted
newest oldest
Inline Feedbacks
View all comments