Configuring Entity Framework Core
Entity Framework (EF) Core, provides a dizzying array of configuration options. In a Clean Architecture, configuring EF Core is straightforward. Let’s explore how.
The Approach
The goal here will be to configure EF Core to use SQL Server for persistence in a Clean Architecture project.
Recall how this was presented in the previous post:
public void ConfigureServices(IServiceCollection services)
{
var options = CreateOptions(Configuration);
services.AddScoped(p => new WarehouseContext(options));
services.AddScoped<IBookRepository, BookRepository>();
}
Here’s an example of what the CreateOptions
routine accomplishes:
private DbContextOptions<WarehouseContext> CreateOptions(IConfiguration configuration)
{
var contextOptions = new DbContextOptionsBuilder<WarehouseContext>();
contextOptions.UseSqlServer(configuration["Database:ConnectionString"]);
return contextOptions.Options;
}
In this example, SQL Server is being configured to work with EF Core. Regardless of the database being used (SqlLite, Oracle, MySQL) they can all be configured in a similar fashion.
Prior to EF Core 3, you may also want to include the following:
var contextOptions = new DbContextOptionsBuilder<SmithContext>();
contextOptions
.UseSqlServer(configuration["Database:ConnectionString"])
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
Adding this warning will alert you to cases where EF Core cannot convert a Linq expression into a SQL query. Without this warning configured to throw an exception, you may experience performance issues because EF Core will only perform part of the filtering using SQL. Here’s a post that goes into more detail:
https://gist.github.com/edgamat/5ed3918e725d35913720667d4237fbeb
Thankfully, with EF Core, this is no longer an option as the ability to include client-evaluated functions in a Linq expression will always throw an exception:
EF Core Migrations
When using EF Core Migrations, you will need to create a design-time factory class that EF Core will use to configure itself when performing migrations.
using System;
using System.IO;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace BookWarehouse.Persistence
{
public class WarehouseContextFactory : IDesignTimeDbContextFactory<WarehouseContext>
{
public WarehouseContext CreateDbContext(string[] args)
{
var runtimePath = GetRuntimePath();
var configuration = new ConfigurationBuilder()
.SetBasePath(runtimePath)
.AddJsonFile("appsettings.json")
.Build();
return new WarehouseContext(CreateOptions(configuration));
}
private static string GetRuntimePath()
{
var executingAssembly = Assembly.GetExecutingAssembly();
var assemblyFile = executingAssembly.Location;
return Path.GetDirectoryName(assemblyFile);
}
public static DbContextOptions<WarehouseContext> CreateOptions(IConfiguration configuration)
{
if (configuration == null)
throw new ArgumentNullException(nameof(configuration));
var contextOptions = new DbContextOptionsBuilder<WarehouseContext>();
contextOptions.UseSqlServer(configuration["Database:ConnectionString"]);
return contextOptions.Options;
}
}
}
The code uses the same appsettings.json
as the Api
project. First, add a link to the existing
file in the BookWarehouse.Persistence.csproj
file:
<ItemGroup>
<Content Include="..\BookWarehouse.Api\appsettings.json" Link="appsettings.json" />
</ItemGroup>
Then add two new NuGet package references to the BookWarehouse.Persistence
project:
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
Now the new design-time factory class will compile.
To generate a new Migration, open a command prompt (or Powershell) in the root folder of the persistence project:
C:\code\BookWarehouse\src\BookWarehouse.Persistence
Run the Entity Framework command for migration creation dotnet ef migrations add <migration name>
Next Steps
So far so good. As your project grows you may find that storing all the model binding details in the
single WarehouseContext
file difficult to manage. Next time we’ll take a look at some techniques
to help manage that and more.