I have read Design-time DbContext Creation that there are 3 ways the EF Core tools (for example, the migration commands) obtain derived DbContext
instance from the application at design time as opposed to at run time.
IDesignTimeDbContextFactory<T>
Here I am only interested in the first method by mimicking the pattern used in Asp.net Core. This code does not compile because I have no idea how to make EF Core tool obtain TheContext
instance.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
public class TheContext : DbContext
{
public TheContext(DbContextOptions<TheContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static readonly IConfiguration _configuration;
private static readonly string _connectionString;
static Program()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
_connectionString = _configuration.GetConnectionString("SqlServer");
}
static void ConfigureServices(IServiceCollection isc)
{
isc.AddSingleton(_ => _configuration);
isc.AddDbContextPool<TheContext>(options => options
.UseSqlServer(_connectionString));
isc.AddSingleton<TheApp>();
}
static void Main()
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
IServiceProvider isp = isc.BuildServiceProvider();
isp.GetService<TheApp>().Run();
}
}
class TheApp
{
readonly TheContext _theContext;
public TheApp(TheContext theContext) => _theContext = theContext;
public void Run()
{
// Do something on _theContext
}
}
How to make EF Core tools obtain DbContext instance from service provider of a console application?
I forgot to mention the appsettings.json
as follows:
{
"ConnectionStrings": {
"Sqlite": "Data Source=MyDatabase.db",
"SqlServer": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True"
}
}
Although the documentation topic is called From application services, it starts with
If your startup project is an ASP.NET Core app, the tools try to obtain the DbContext object from the application's service provider.
Looks like they don't expect project types other than ASP.NET Core app to use application service provider :)
Then it continues with
The tools first try to obtain the service provider by invoking Program.BuildWebHost() and accessing the IWebHost.Services property.
and example:
public static IWebHost BuildWebHost(string[] args) => ...
And here is the trick which works with the current (EF Core 2.1.3) bits. The tools actually are searching the class containing your entry point (usually Program
) for a static (does not need to be public) method called BuildWebHost
with string[] args
parameters, and the important undocumented part - the return type does not need to be IWebHost
! It could be any object having public property like this
public IServiceProvider Services { get; }
Which gives us the following solution:
class Program
{
// ...
// Helper method for both Main and BuildWebHost
static IServiceProvider BuildServiceProvider(string[] args)
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
return isc.BuildServiceProvider();
}
static void Main(string[] args)
{
BuildServiceProvider(args).GetService<TheApp>().Run();
}
// This "WebHost" will be used by EF Core design time tools :)
static object BuildWebHost(string[] args) =>
new { Services = BuildServiceProvider(args) };
}
Update: Starting from v2.1, you could also utilize the new CreateWebHostBuilder
pattern, but IMHO it just adds another level of complexity not needed here (the previous pattern is still supported). It's similar, but now we need a method called CreateWebHostBuilder
which returns an object having public method Build()
returning object having public Services
property returning IServiceProvider
. In order to be reused from Main
, we can't use anonymous type and have to create 2 classes, and also this makes it's usage from Main
more verbose:
class AppServiceBuilder
{
public ServiceCollection Services { get; } = new ServiceCollection();
public AppServiceProvider Build() => new AppServiceProvider(Services.BuildServiceProvider());
}
class AppServiceProvider
{
public AppServiceProvider(IServiceProvider services) { Services = services; }
public IServiceProvider Services { get; }
}
class Program
{
// ...
static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Services.GetService<TheApp>().Run();
}
// This "WebHostBuilder" will be used by EF Core design time tools :)
static AppServiceBuilder CreateWebHostBuilder(string[] args)
{
var builder = new AppServiceBuilder();
ConfigureServices(builder.Services);
return builder;
}
}
Try this, i've added some changes to make it run :
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.FileExtensions;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
namespace IOCEFCore
{
public class TheContext : DbContext
{
public TheContext(DbContextOptions<TheContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static readonly IConfigurationRoot _configuration;
private static readonly string _connectionString;
static Program()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
}
static void ConfigureServices(IServiceCollection isc)
{
isc.AddSingleton(_ => _configuration);
isc.AddDbContextPool<TheContext>(options => options.UseInMemoryDatabase("myContext"));
isc.AddSingleton<TheApp>();
}
static void Main()
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
IServiceProvider isp = isc.BuildServiceProvider();
isp.GetService<TheApp>().Run();
Console.ReadLine();
}
class TheApp
{
readonly TheContext _theContext;
public TheApp(TheContext theContext) => _theContext = theContext;
public void Run()
{
// Do something on _theContext
_theContext.Users.Add(new User {Id = 1, Name = "Me"});
_theContext.SaveChanges();
foreach (var u in _theContext.Users)
{
Console.WriteLine("{0} : {1}", u.Id, u.Name);
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With