EF Core in a separate class library

·

3 min read

Whenever I start a new .NET Core project I like to separate out parts of the app into individual projects. Traditionally, I break down a solution like this:

  • [Solution Name].Data - contains models, migrations and database context classes
  • [Solution Name].Logic - contains services, extensions and mapper profiles
  • [Solution Name].Web - contains all web layer components

So my DB context class will be in the Data project which is a class library. The Web project will then reference this project. The data project needs a couple of packages that allow it to create the migrations and use EF core.

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.4" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.4">
</ItemGroup>

Next, we need to setup the DBContext class with the following two constructors

public TestDBContext(DbContextOptions options) : base(options)
{
}

protected TestDBContext()
{
}

Now that we have the DBContext class setup we need to be able to consume it from the Web project. In order to do that we need to add a couple of ef core packages and reference the Web project.

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.6" />
</ItemGroup>

<ItemGroup>
    <ProjectReference Include="..\\\[Solution Name\].Data\\AlumniOptin.Data.csproj" />
    <ProjectReference Include="..\\\[Solution Name\].Logic\\AlumniOptin.Logic.csproj" />
</ItemGroup>

The next step is to update the ConfigureServices method (in Startup.cs) to inject the context to the pipeline. As we are using a different assembly for the migrations we need to explicitly specify it.

services.AddDbContext<TestDBContext>
    (options =>
    {
    options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection"),
         assembly => assembly.MigrationsAssembly(typeof(TestDBContext).Assembly.FullName));
    });

Now that everything is configured we can try and create a migration! This should use the connection string in the appsettings.json (or secrets.json if specified) and migrations should be created in the Data project.

Creating a migration

To add a migration, you will need to open up the Visual Studio Solution and complete the following within the Package Manager Console window (this can also be done in a normal console window in the working directory of the solution):

  1. Type the following at the command prompt (PM>): dotnet ef migrations add '[MIGRATION_NAME]' --project .\src\[Project Name].Data\[Project Name].Data.csproj --startup-project .\src\[Project Name].Web\[Project Name].web.csproj
    • [MIGRATION_NAME] - Name of the migration. This should be used to identify what has changed. If you add a new column to a table then you could call it something like Add[NewColumnName][TableName]
    • The tool will look at the migrations already applied on the supplied database. If it's missing a migration then you will need to apply it (see below) before adding a new migration.
  2. A new [MIGRATION_NAME].cs file will be generated and added to the Migrations folder within the Data project. This will contain the Up and Down actions that will be applied to the database. You can refine the code in the methods to complete certain actions.

Applying a migration

To apply any changes to a database that is deployed locally (i.e. not via a release pipeline). You will need to use the ef core ef database update command.

Open up the Visual Studio Solution and inside the Package Manager Console:

  1. Type the following at the command prompt (PM>): dotnet ef database update --project .\src\[Project Name].Data\[Project Name].Data.csproj --startup-project .\src\[Project Name].Web\[Project Name].web.csproj
  2. By default, the command will attempt to identify if any migrations need to be applied to the database. If it does, then it will apply the migrations sequentially.