Entity Framework Core 3.0 - Put DbContext, entities and migrations in a separated library project

.NET Core and more generally .NET applications source code is split between multiple projects (.csproj file) which can be grouped into what is called a solution (.sln file). When you are creating a small application, you can put everything in only one project, but I would rarely recommend it. As soon as the application become bigger, you will need to separate different parts of it which is a design principle known as separation of concerns.

A typical application's solution would look like this:

I will not go in details about the whole structuring of a .NET Core application in this post, but only show you how to separate all EF Core/database related stuff (DbContext, entities, migrations) from your main project and put it into a library project.

Let's begin!

Before doing anything on your project, I recommend you to install the EF Core global tools by typing this command:

dotnet tool install --global dotnet-ef

Having the dotnet-ef tool installed as global will enables you to use it wherever you want without adding it to a specific project. This tool allows you to manage migrations, your database and more.

The first step is to create the new library project and add it to your solution. Either by using an IDE or typing the following commands: (no project naming convention is required but I usually use "ApplicationName.Database"):

dotnet new classlib -n MyApplication.Database -f netcoreapp3.0 //Create the project

dotnet sln add MyApplication.Database //Add the project to the solution

Once the project is created and added to the solution, you can import it to whatever projects needing it.

Next step is to import the required NuGet packages: Microsoft.EntityFrameworkCore.Design and Microsoft.EntityFrameworkCore.Abstractions

If you already have a DbContext, some migrations and entities in another project, you can move them to the new project and fix namespace/missing libraries issues (ReSharper or Rider is highly recommended here). Else you can create one simply by adding a new class inheriting DbContext and adding a constructor like this:

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

Do not forget to add the DbContext to your service collection if needed by calling serviceCollection.AddDbContext<MyApplicationDbContext> in your startup class.

Then you can create your different entities and add them to your context as usual. Your DbContext might end up looking like this:

public class MyApplicationDbContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    
    public DbSet<Comment> Comments { get; set; }
    
    public DbSet<Account> Accounts { get; set; }
    
    public MyApplicationDbContext(DbContextOptions<MyApplicationDbContext> options) : base(options)
    {
    }
}

Right now, if you try to generate a migration, it should throws an exception saying "Unable to create an object of type 'MyApplicationDbContext'..." This is totally normal, all you need to do is to tell EF Core how to create a DbContext.

Actually, there is two ways to do it: specify the "startup" project when using dotnet ef commands or create a IDesignTimeDbContextFactory<> in the database project.

To specify your application startup project when using dotnet ef commands, you simply need to add -s pathToStartupProject to your command. Example to generate the initial/first migration and then update the database:

dotnet ef migrations add InitialMigration -p pathToDatabaseProject -s pathToStartupProject

dotnet ef database update -p pathToDatabaseProject

When specifying a startup project, it will use the connection string set in the startup project.

If you don't want to specify the startup project on every command or you'd like to use a database specifically for design time, the other option is to create a custom IDesignTimeDbContextFactory in your "database project" like the following:

public class MyApplicationDbContextFactory : IDesignTimeDbContextFactory<MyApplicationDbContext>
{
    public MyApplicationDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<MyApplicationDbContext>()
            .UseSqlite("Data Source=app.db");
        return new MyApplicationDbContext(optionsBuilder.Options);
    }
}

This example uses the official SQLite provider, but you can use whatever you want (it still needs to be the same provider used in your startup project).

Now you can create migrations and update database without specifying the startup project:

dotnet ef migrations add InitialMigration -p pathToDatabaseProject

dotnet ef database update -p pathToDatabaseProject

If you open your terminal directly in the database project directory, you don't even need to set the project parameter (-p).

Hope you guys enjoyed it! Please comment if you have any question or suggestion.

What's next?

You can put entity classes in another project too, if you need even more separation.