Demonstrates how to add DI to IDesignTimeDbContextFactory implementation so that the EF Core CLI will pick up the connection string via DI and Config.
- .NET Core SDK
- https://dotnet.microsoft.com/download/
- This demo uses .NET Core 2.2.
-
Create a new Visual Studio solution and add a global.json file with the SDK version.
- Open a command prompt at the solution root.
- Enter and run:
dotnet new globaljson
- Add an existing item to the solution and select the global.json file.
- Verify that the SDK version matches that which you previously installed.
-
Add an ASP.NET Core Web API project.
- Use .Web suffix.
-
Add a .NET Standard Class Library project for entities.
- Use .Entities suffix.
- Add a
Product
class.
public class Product { public int Id { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } }
-
Add a .NET Standard Class Library project for repository interfaces.
- Use .Abstractions suffix.
- Reference the Entities project.
- Add
IProductRepository
interface.
public interface IProductRepository { Task<Product> GetProduct(int id); }
-
Add a .NET Standard Class Library for EF context and repositories.
- Use .EF suffix.
- Reference Entities and Abstractions projects.
- Add Microsoft.EntityFrameworkCore.Sql NuGet package.
- Add
ProductsDbContext
class.- Include ctor that accepts
DbContextOptions
. - Override
OnModelCreating
to seed data.
- Include ctor that accepts
public class ProductsDbContext : DbContext { public ProductsDbContext(DbContextOptions options) : base(options) { } public DbSet<Product> Products { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { var product = new Product { Id = 1, ProductName = "Chai", UnitPrice = 10 }; modelBuilder.Entity<Product>().HasData(product); } }
- Add
ProductRepository
class.- Add ctor accepting
ProductsDbContext
.
- Add ctor accepting
public class ProductRepository : IProductRepository { public ProductsDbContext Context { get; } public ProductRepository(ProductsDbContext context) { Context = context; } public async Task<Product> GetProduct(int id) { return await Context.Products.SingleOrDefaultAsync(e => e.Id == id); } }
-
Add a connection string to the appsettings files in the Web project.
- Use different database names to simulate Dev vs Prod deployments.
- Add the following to appsettings.json:
"ConnectionStrings": { "ProductsDbContext": "Data Source=(localdb)\\MsSqlLocalDb;initial catalog=ProductsDbProd;Integrated Security=True; MultipleActiveResultSets=True" }
- Add the following to appsettings.Development.json:
"ConnectionStrings": { "ProductsDbContext": "Data Source=(localdb)\\MsSqlLocalDb;initial catalog=ProductsDbPDev;Integrated Security=True; MultipleActiveResultSets=True" }
-
Configure DI in the Web project.
- Add Microsoft.EntityFrameworkCore.Sql NuGet package.
- Add references to the Entities, Abstractions and EF projects.
- Update
ConfigureServices
inStartup
to registerProductsDbContext
with the DI system.
services.AddDbContext<ProductsDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString(nameof(ProductsDbContext)))); services.AddScoped<IProductRepository, ProductRepository>();
-
Refactor
ValuesController
in the Web project to useIProductRepository
to retrieve a product by id.- Rename to
ProductsController
- Add ctor accepting
IProductRepository
.
public IProductRepository ProductsRepo { get; } public ProductsController(IProductRepository productsRepo) { ProductsRepo = productsRepo; }
- Remove all methods except
Get(int id)
. - Refactor
Get
method.
// GET api/products/5 [HttpGet("{id}")] public async Task<ActionResult<Product>> Get(int id) { var product = await ProductsRepo.GetProduct(id); return Ok(product); }
- Rename to
-
Add a .NET Core Class Library project with a .EF.Design suffix.
-
This needs to be a .NET Core, versus .NET Standard, project in order to support the EF Core CLI tooling.
-
Add the following NuGet packages:
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.SqlServer -
Add a
ProductsDbContextFactory
class that implementsIDesignTimeDbContextFactory<ProductsDbContext>
.
public class ProductsDbContextFactory : IDesignTimeDbContextFactory<ProductsDbContext> { public ProductsDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder<ProductsDbContext>(); var connectionString = "Data Source=(localdb)\\MsSqlLocalDb;initial catalog=ProductsDbDev;Integrated Security=True; MultipleActiveResultSets=True"; optionsBuilder.UseSqlServer(connectionString, b => b.MigrationsAssembly("EfDesignDemo.EF.Design")); return new ProductsDbContext(optionsBuilder.Options); } }
-
-
This should be sufficient to generage a code migration and create a database.
- Run the following from the EF.Design project directory.
dotnet ef migrations add initial
- Then run the following to create the database.
dotnet ef database update
-
Try running the application.
- First
cd
into the Web application directory.
dotnet run
- Then browse to: http://localhost:5000/api/products/1
- A product should be retrieved from the database.
{ id: 1, productName: "Chai", unitPrice: 10 }
- Press Ctrl+C to terminate the running app.
- First
-
Refactor
ProductsDbContextFactory
to retrieve the connection string from the appsettings.*.json file.- This depends on the value of the
ASPNETCORE_ENVIRONMENT
environment variable.
public class ProductsDbContextFactory : IDesignTimeDbContextFactory<ProductsDbContext> { public ProductsDbContext CreateDbContext(string[] args) { // Get environment string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); // Build config IConfiguration config = new ConfigurationBuilder() .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../EfDesignDemo")) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{environment}.json", optional: true) .AddEnvironmentVariables() .Build(); // Get connection string var optionsBuilder = new DbContextOptionsBuilder<ProductsDbContext>(); var connectionString = config.GetConnectionString(nameof(ProductsDbContext)); optionsBuilder.UseSqlServer(connectionString, b => b.MigrationsAssembly("EfDesignDemo.EF.Design")); return new ProductsDbContext(optionsBuilder.Options); } }
- This depends on the value of the
-
From the EF.Design project directory run
dotnet ef database update
.- This time the ProductsDbProd database should be created from the connection string in appsettings.json.
- This is because the
ASPNETCORE_ENVIRONMENT
environment variable is not set.
-
Try setting
ASPNETCORE_ENVIRONMENT
on the command line before runningdotnet ef database update
.set ASPNETCORE_ENVIRONMENT=Development dotnet ef database update
- First delete the ProductsDbDev database.
- This time the ProductsDbDev database should be created.
-
Add some interfaces to the to the .Abstractions project.
- Add a
IConfigurationService
interface.- Add the Microsoft.Extensions.Configuration.Abstractions NuGet package.
public interface IConfigurationService { IConfiguration GetConfiguration(); }
- Add a
IEnvironmentService
interface.
public interface IEnvironmentService { string EnvironmentName { get; set; } }
- Add a
-
Add a .Configuration .NET Standard Class Library project to the solution.
- Add the following NuGet packages:
Microsoft.Extensions.Configuration Microsoft.Extensions.Configuration.EnvironmentVariables Microsoft.Extensions.Configuration.Json
- Reference the .Abstractions project.
- Add an
EnvironmentService
class.
public class EnvironmentService : IEnvironmentService { public EnvironmentService() { EnvironmentName = Environment.GetEnvironmentVariable(Constants.EnvironmentVariables.AspnetCoreEnvironment) ?? Constants.Environments.Production; } public string EnvironmentName { get; set; } }
- Add an
ConfigurationService
class.
public class ConfigurationService : IConfigurationService { public IEnvironmentService EnvService { get; } public string CurrentDirectory { get; set; } public ConfigurationService(IEnvironmentService envService) { EnvService = envService; } public IConfiguration GetConfiguration() { CurrentDirectory = CurrentDirectory ?? Directory.GetCurrentDirectory(); return new ConfigurationBuilder() .SetBasePath(CurrentDirectory) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{EnvService.EnvironmentName}.json", optional: true) .AddEnvironmentVariables() .Build(); } }
-
Add a .DI .NET Standard Class Library project.
- Add the following NuGet packages.
Microsoft.EntityFrameworkCore.Design Microsoft.EntityFrameworkCore.SqlServer
- Add references to the .EF,** .Abstractions**, .Configuration projects.
- Add a
DependencyResolver
class.
public class DependencyResolver { public IServiceProvider ServiceProvider { get; } public string CurrentDirectory { get; set; } public DependencyResolver() { // Set up Dependency Injection IServiceCollection services = new ServiceCollection(); ConfigureServices(services); ServiceProvider = services.BuildServiceProvider(); } private void ConfigureServices(IServiceCollection services) { // Register env and config services services.AddTransient<IEnvironmentService, EnvironmentService>(); services.AddTransient<IConfigurationService, ConfigurationService> (provider => new ConfigurationService(provider.GetService<IEnvironmentService>()) { CurrentDirectory = CurrentDirectory }); // Register DbContext class services.AddTransient(provider => { var configService = provider.GetService<IConfigurationService>(); var connectionString = configService.GetConfiguration().GetConnectionString(nameof(ProductsDbContext)); var optionsBuilder = new DbContextOptionsBuilder<ProductsDbContext>(); optionsBuilder.UseSqlServer(connectionString, builder => builder.MigrationsAssembly("EfDesignDemo.EF.Design")); return new ProductsDbContext(optionsBuilder.Options); }); } }
-
Add a reference from the .EF.Design project to the .DI project.
- Refactor the
ProductsDbContextFactory
to use theDependencyResolver
to create theDbContext
.
public class ProductsDbContextFactory : IDesignTimeDbContextFactory<ProductsDbContext> { public ProductsDbContext CreateDbContext(string[] args) { // Get DbContext from DI system var resolver = new DependencyResolver { CurrentDirectory = Path.Combine(Directory.GetCurrentDirectory(), "../EfDesignDemo.Web") }; return resolver.ServiceProvider.GetService(typeof(ProductsDbContext)) as ProductsDbContext; } }
- Refactor the