Setting up Entity Framework in .Net Core can at times seem difficult.
It’s easy to hit speed bumps like not being able to set up the tooling in a console project, or struggling to have a DbContext in separate a classlibrary.
In this blog post we’ll go through all that, starting with how to manually set up the tooling (i.e. enable dotnet ef
), how to have a DbContext in separate class library and also how to use migrations even when your DbContext lives in a different project.
Tooling
The goal here is to enable being able to use dotnet ef
from the command line to generate migrations and create and update a database.
If you use some of the available templates when creating a new project, for example MVC with Individual User Accounts (dotnet new mvc -au Individual
), you’ll notice that if you navigate to the project in a terminal window and type dotnet ef
everything will be configured for you.
This is the output you should get:
_/\__
---==/ \\
___ ___ |. \|\
| __|| __| | ) \\\
| _| | _| \_/ | //|\\
|___||_| / \\\/\\
Entity Framework Core .NET Command Line Tools 2.0.0-rtm-26452
Usage: dotnet ef [options] [command]
This won’t work if you either create a console application (dotnet new console
) or even an empty MVC application (dotnet new mvc
).
Also, it won’t be enough to add the NuGet package that enables the tooling, you need to know how the tooling finds and instantiates your DbContext.
DbContext Creation
When using Entity Framework (EF) you invariantly have to create a subclass of DbContext. That along with the DbSet<T>
properties in your derived DbContext are the information that EF uses to create migrations, update the database, etc.
The first thing that happens when you type dotnet ef
in a project is that the tooling will try to find and create your derived DbContext. And this is where MVC projects get special treatment.
You might have noticed that if you create an empty MVC project and manually setup EF tooling (we’ll get to how to do that shortly), the tooling will just work.
If you do exactly the same thing in a console application, it won’t.
The first thing that happens when you type dotnet ef
is that the static method Program.BuildWebHost()
is invoked which returns an instance of IWebHost
.
IWebHost
is where you define where your Startup
class is, and consequently where the ConfigureServices
method lives.
ConfigureServices
is where you normally find the DbContext being configured, for example:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
//...
IWebHost
exposes a Services
property of type IServiceProvider
, which you can think of as the IoC container for .Net Core.
It’s from there that the tooling gets to the derived and configured DbContext (e.g. serviceProvider.GetService<YourDbContext>()
)
Obviously this won’t work for a console application (there’s no BuildWebHost
method in Program
).
Thankfully there are two other ways that the tooling can use to create the DbContext that don’t require Program.BuildWebHost
.
The simplest one is to have a derived DbContext with a parameterless constructor that overrides the OnConfiguring
method. For example if your DbContext is named MyDbContext
and you are using Sqlite:
public class MyDbContext : DbContext
{
//...
public MyDbContext() {}
//...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=mydb.sqlite");
}
}
Finally, there’s one extra method that takes precedence over the others. dotnet ef
will look for a class that implements IDesignTimeDbContextFactory<T>
in the project that contains the DbContext derived class or the project where you are running donet ef
under (this is the project where you navigate to in the terminal and type donet ef
).
Here’s how that would look like if your DbContext is named MyDbContext
and you are using for example Sqlite
:
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args) //you can ignore args, maybe on later versions of .net core it will be used but right now it isn't
{
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite("Data Source=mydb.sqlite");
return new MyDbContext(optionsBuilder.Options);
}
}
Note that IDesignTimeDbContextFactory<T>
is in the Microsoft.EntityFrameworkCore.Design
namespace and is available from the NuGet package with the same name.
Adding EF to a Console Application
Knowing how to add Entity Framework to a .Net Core console application is useful since you’ll have to be familiar with all the steps required for it to be configured correctly.
First thing you need to do is to install the Microsoft.EntityFrameworkCore.Design
nuget package:
$ dotnet add package Microsoft.EntityFrameworkCore.Design
This is the package that contains all we need to be able to create migrations and that the tooling relies to be able to create your DbContext.
You’ll also need to add the EF Core package for the particular database you are targeting, for example Sqlite:
$ dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Finally you need to add the NuGet package for the tooling. Unfortunately there’s no way to do this without manually editing the .csproj
file (There’s currently an unresolved issue for it in github). It needs to be added in a different way than a normal NuGet package (instead of PackageReference
, DotNetCliToolReference
is used instead in a separate ItemGroup
).
Open your .csproj file and add:
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="SAME VERSION AS Microsoft.EntityFrameworkCore.Design"/>
</ItemGroup>
Note that if this were a ASP.NET Core application the version would have to match that of the Microsoft.AspNetCore.All
NuGet package. That’s because Microsoft.AspNetCore.All
is a metapackage (package that just has dependencies on other packages) which contains Microsoft.EntityFrameworkCore.Design
.
Now the only thing that is missing is how to create your DbContext derived class in your console application. If you use the method of overriding the OnConfiguring
method you just need to create a new instance of your DbContext.
Alternatively, if you used IDesignTimeDbContextFactory
you can use DbContextOptionsBuilder
to create the options you need to pass to your derived DbContext’s constructor, for example:
class Program
{
static void Main(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
var myDbContext = new MyDbContext(optionsBuilder.UseSqlite("Data Source=databaseName.sqlite").Options);
//...
Note that when you do this you’ll have two places where you use the connection string, in the class that implements IDesignTimeDbContextFactory<T>
and when you manually create your DbContext.
As an aside, if you want to be able to use dependency injection to create your DbContext check out my other article Logging in .Net Core Console Apps which describes all the steps you need to perform to enable Dependency Injection in a console application (and how to use it to enable the logging features that come “out of the box” with .Net Core).
That’s all you need to have the tooling work if your DbContext is inside your console application. Having the DbContext in a separate class library project requires a little bit more work.
EF Core Class Library Project
One thing that you probably will want to do is to have your data access code (your models and your DbContext derived classes) in a separate class library project.
First thing you need to do is to install the Microsoft.EntityFrameworkCore
NuGet package in the class library project:
$ dotnet add package Microsoft.EntityFrameworkCore
This is the only package required in the class library.
On your running project that references the class library setup the tooling as described before in the blog post.
After that, if your running project is a console application add an implementation for IDesignTimeDbContextFactory<T>
for your DbContext.
We also need to specify that we want the migrations to be created on the running project. That is achieved using the .MigrationsAssembly
method. If we don’t do this we’ll get this error: “Your target project ‘ConsoleProject’ doesn’t match your migrations assembly ‘TheClassLibrary’. Either change your target project or change your migrations assembly….”
For example if you DbContext is named MyDbContext and your running project is named ConsoleProject here’s how that would look:
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
return new MyDbContext(optionsBuilder.UseSqlite("Data Source=mydb.sqlite", options => options.MigrationsAssembly("ConsoleProject")).Options);
}
}
If your project is an ASP.NET Core application you can just apply the necessary configuration in the ConfigureServices
method, for example in Startup.cs
if you project is named WebProject
:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlite("Data Source=mydb.sqlite",
optionsBuilder => optionsBuilder.MigrationsAssembly("WebProject")));
//...
Now, from your runnable project you can use the tooling to create migrations (donet ef migrations add
), update the database (dotnet ef database update
), etc.