Entity Framework Core Using Multiple Dbcontexts

Entity Framework Core Using multiple DbContexts

I figured it out. This mostly came about because I accidentally deleted the database that Identity was using and I needed to figure out how to get it back.

Apparently there's nothing wrong with my connection string the way it is. I just needed to go into the package manager and type these commands in this order:

  1. Add-Migration init -Context PartsDbContext
  2. Update-Database -Context PartsDbContext

I found this out because that is what I had to do to get my ApplicationDbContext working again and it turns out that this step is done for you when you create a new MVC Core Web Application in Visual Studio using Individual User Authentication.

So basically the steps for adding more DbContexts is to:

  1. Create a DbContext Class
  2. Create a Connection string for that DbContext in appsettings.json
  3. Add the DbContext to your configured services in Startup.cs
  4. Setup the DbContext in the controllers that will use it.
  5. Open the package manager and run the 2 lines above. (if "-Context" doesn't work try "--context"
  6. Run your program and let EntityFrameworkCore take care of the rest.

Entity Framework: multiple dbcontext or not? And a few other performance related question

Most of your performance pain points will come from a complex architecture that you have not simplified. Managing a monolithic application result in lots of unnecessary 'compensating' logic when one use case is treading on the toes of another and your relationships are so intertwined.

Optimisations such as whether to use Context Pooling or IEnumerable vs ICollection can come later. They should not affect the architecture of your solution.

If your project is as complex as you suggest, then I'd recommend you read up on Domain Driven Design and Microservices and break your application up into several projects (or groups of projects).

https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/

Each project (or group of projects) will have its own DbContext to administer the entities within that project.
Further, each DbContext should start off by only exposing Aggregate Roots through the DbSets. This can mean more database activity than is strictly necessary for a particular use case but best to start with a clean architecture and squeeze that last ounce of performance (sometimes at the cost of architectural clarity), if and when needed.

For example if you want to add an attendee to an appointment, it can be appealing to attack the Attendee table directly. But, to keep things clean, and considering an attendee cannot exist without an appointment, then you should make appointment the aggregate root and only expose appointment as an entry point for the outside world to attack. That appointment can be retrieved from the database with its attendees. Then ask the appointment to add the attendees. Then save the appointment graph by calling SaveChanges on the DbContext.

In summary, your Appointment is responsible for the functionality within its graph. You should ask Appointment to add an Attendee to the list instead of adding an Attendee to the Appointment's list of attendees. A subtle shift in thinking that can reduce complexity of your solution an awful lot.

The art is deciding where those boundaries between microservices/contexts should lie. There can be pros and cons to two different architectures, with no clear winner


To your other questions:

DbContext Pooling is about maintaining a pool of ready-to-go instantiated DbContexts. It saves the overhead of repeated DbContext instantiation. Probably not worth it, unless you have an awful lot of separate requests coming in and your profiling shows that this is a pain point.

The number of DbSets required is alluded to above.

As for IEnumerable or ICollection or IList, it depends on what functionality you require. Here's a nice simple summary ... https://medium.com/developers-arena/ienumerable-vs-icollection-vs-ilist-vs-iqueryable-in-c-2101351453db

Would it be better/smarter to split the application into smaller
projects?

Yes, absolutely! Start with architectural clarity and then tweak for performance benefits where and when required. Don't start with performance as the goal (unless you're building a millisecond sensitive solution).

Registering multiple DBContexts in .net core with same base class

So the real answer here was nested in a github answer from three years ago that can be found https://github.com/dotnet/efcore/issues/7533. The issue comes down to how the services are being rendered for pooling, which is why when registering multiple DbContexts that you have to specify the type on the DbContextOptions. To get around this on the base class you can have a protected constructor for the options. This will allow the pooling to work correctly and allow you to inherit the context. The example given by greggbjensen on Dec 2017 is below and was exactly what I was looking for.

public class MainDbContext : DbContext
{
public MainDbContext(DbContextOptions<MainDbContext> options)
: base(options)
{
}

protected MainDbContext(DbContextOptions options)
: base(options)
{
}
}

public class SubDbContext : MainDbContext
{
public SubDbContext (DbContextOptions<SubDbContext> options)
: base(options)
{
}
}

This allows for two services to be set up in the .net core services code as such.

services.AddDbContext<MainDbContext >(options => [[SomeOptionsHere]]);
services.AddDbContext<SubDbContext >(options => [[SomeOptionsHere]]);

Multiple DBContext at run time with multiple providers

You can achieve your goal by creating a factory class.

Assuming you have a DbContext class that accepts a DbContextOptions<T>:

class AppDbContext: DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}

We can replicate how EF Core registers and instantiates a DbContext internally in the factory and generate & supply the options dynamically.

I'm using ConnectionConfiguration as a generic placeholder for connection info. You might prefer to store this info in Customer entity.

record ConnectionConfiguration(string Provider, string ConnectionString);

class DynamicDbContextFactory<TContext> where TContext: DbContext
{
private readonly IServiceProvider _serviceProvider;
private Func<IServiceProvider, DbContextOptions<TContext>, TContext> _factory;

public DynamicDbContextFactory(IServiceProvider serviceProvider, IDbContextFactorySource<TContext> factorySource)
{
_serviceProvider = serviceProvider;
_factory = factorySource.Factory;
}

public Task<TContext> CreateDbContextAsync(ConnectionConfiguration connection)
{
var builder = new DbContextOptionsBuilder<TContext>()
.UseApplicationServiceProvider(_serviceProvider);
builder = connection.Provider switch
{
"sqlite" => builder.UseSqlite(connection.ConnectionString),
"npgsql" => builder.UseNpgsql(connection.ConnectionString),
_ => throw new InvalidOperationException("No such provider")
};
var db = _factory(_serviceProvider, builder.Options);
return Task.FromResult(db);
}
}

Here I'm using IDbContextFactorySource, which is an internal type with no support guarantee, and raises warnings during compilation. But you can simply copy its source code for finding a suitable constructor & creating a factory method.

Then we need to register a couple of services:

services.AddDbContext<AppDbContext>(db => db.UseInMemoryDatabase(nameof(AppDbContext)));
services.AddSingleton<IDbContextFactorySource<AppDbContext>, DbContextFactorySource<AppDbContext>>();
services.AddSingleton(typeof(DynamicDbContextFactory<>)); // register as open generic type and let DI close the type during resolution

Then we can resolve this factory from the service provider and create an instance on the fly:

class CustomerService
{
private DynamicDbContextFactory<AppDbContext> _contextFactory;

public CustomerService(DynamicDbContextFactory<AppDbContext> contextFactory)
{
_contextFactory = contextFactory;
}

public async Task SaveThingsAsync()
{
// fetch connection info from somewhere
var conn = new ConnectionConfiguration(Provider: "sqlite", ConnectionString: "Data Source=:memory:");
await using var db = await _contextFactory.CreateDbContextAsync(conn);

await db.Set<Sales>().AddAsync(new Sale( /*...*/));
await db.SaveChangesAsync();
}
}

Note: Since you're the one creating the DbContext, you should also dispose it when you're done with it (with a using statement, or by implementing IDisposable).

Entity framework multiple DbContext in single execution

One option to accommodate this cleanly, especially for something as frequently accessed as a "User" reference for something like reporting on CreatedBy or ModifiedBy tracking on rows would be to implement a view within Db2 that lists the users from Db1. Then in your main application context you can map a User entity to the view rather than a table. I would put guards in your DbContext and entities to discourage/prevent modifications to this User entity, and leave maintenance of users to a DbContext overseeing the Db1 tables.

If this is something like a multi-tenant system with a system database for authentication and separate DBs per tenant which are tracking things like CreatedBy against records, I would recommend considering a system to inspect and replicate users between the auth database and the respective tenant databases. The reason for this would be to help enforce referential integrity for the data and the user references. The issue with the view approach is that there is no constraint available to ensure that a UserId reference actually corresponds with a row in the Users table over in the other database. It can be indexed, but not constrained so you have to handle the possibility of invalid data.

EF 7 Migrations with multiple DBContexts

I was looking for an answer to this question and wanted to provide my solution for ef core 2.0.

Microsoft.EntityFrameworkCore.Tools.DotNet needs to be added to each of your class libraries that have a DbContext in them. Right click the project and select Edit *.csproj. Then, add the following:

  <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0-preview2-final" />
</ItemGroup>

Note: the version is the latest at the time of this post and will likely change in the future.

Next, I created a new Console App (.NET Core) called Migrations.Console and added it to my Solution. You will have to reference all of your DbContext class libraries in this project.

I installed Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.Design Nuget packages.

In the Migrations.Console app, I created a DbContextFactory class for each Db context I have.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseSqlServer("Server=(local);Database=DATABASENAME;Trusted_Connection=True;MultipleActiveResultSets=true");
return new ApplicationDbContext(builder.Options);
}
}

Note: Make sure you update the context and connection string to match your project.

Now that each DbContextFactory is created, you can start to create the migrations. Go to the folder for your class library. The easiest way it to right click the project and Open Folder in File Explorer. Then, type cmd in the address bar of the File Explorer to open a command prompt in that folder.

Now use the following command to create the migration:

dotnet ef migrations add InitialCreate -c ApplicationDbContext --startup-project ../Migrations.Console/Migrations.Console.csproj

Note: Change ApplicationDbContext to match the name of the context you are working with. Also, if you called the console project by another name, you will need to change the path and name.

You should now see a Migrations folder in your class library.



Related Topics



Leave a reply



Submit