1
votes

Note I have read the large number of SO answers that appear to be similar, but I am already doing what they suggested, so I don't know if there is some difference with WPF (they all seem to relate to ASP.NET). Also, most answers relate to run-time errors, not ones when adding migrations.

I'm trying to set up a .NET Core 3 WPF project that uses EntityFrameWork Core, but am having problems adding a migration. I have set up my context as follows...

  public class ApplicationDbContext : DbContext {
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
      : base(options) {
    }

    public ApplicationDbContext() {
    }

    public DbSet<Product> Products { get; set; }
  }

The parameterless constructor is there, as without it I get an exception Unable to create an object of type 'ApplicationDbContext' when trying to add a migration.

My App.xaml.cs contains the following...

  public partial class App {
    public IServiceProvider ServiceProvider { get; private set; }

    public IConfiguration Configuration { get; private set; }

    protected override void OnStartup(StartupEventArgs e) {
      IConfigurationBuilder builder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appSettings.json", optional: false, reloadOnChange: true);

      Configuration = builder.Build();

      ServiceCollection serviceCollection = new ServiceCollection();
      ConfigureServices(serviceCollection);

      ServiceProvider = serviceCollection.BuildServiceProvider();

      MainWindow mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
      mainWindow.Show();
    }

    private void ConfigureServices(IServiceCollection services) {
      // Configuration
      services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
      // Database
      services.AddDbContext<ApplicationDbContext>(options => 
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")));
      // Windows
      services.AddTransient(typeof(MainWindow));
    }
  }

I realise that some of this is irrelevant, but thought I'd show the whole class in case it reveals something I missed. The code is based on this blog post.

However, when I try to add a migration, I get an exception "No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext."

As far as I can see, I have configured the database provider. I put a breakpoint in the ConfigureServices method, and can see that services.AddDbContext is called with the correct connection string.

Anyone any ideas what I've missed?

UPDATE I tried connecting to an existing database, and it worked absolutely fine, so it looks like the database provider has been configured correctly. It's only when I try to add a migration that I get the exception.

UPDATE 2 It seems that the migration tool is using the parameterless constructor on the context, which is why it thinks the provider hasn't been configured. If I remove the lines that configure it from App.xaml.cs, and instead override the OnConfiguringmethod to call UseSqlServer then the migration works fine. However, apart from the fact that I've not seen anyone else doing this (which makes me wonder if it's really the right way to do it), I don't see how to get the connection string from the config file. I can't inject an IConfiguration parameter, as the whole issue is that migrations requires a parameterless constructor.

2
Did you sort this out? I'd also like to know how to do it.DreamingOfSleep
@DreamingOfSleep No I didn't. Can't believe we're the only people to have tried this!Avrohom Yisroel
Giving up. The client has agreed to a web application instead of WPF, and I'm fed up of wasting my time on something that seems to be so badly broken and poorly documented that it makes me wonder if anyone is actually using it.Avrohom Yisroel

2 Answers

1
votes

It's actually quite simple with .Net Core 3.1 and EF Core Version 5, Entity Framework will look at the entry point class for the static function CreateHostBuilder, in my case that would be the App class in app.xaml.cs.

Not entirely sure the convention required prior to .Net Core 3.1. From my experience it had something to do with having a Startup class with .Net Core 2.1 and ASP.Net.

https://docs.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli

My solution:

public partial class App : Application
{
    /// <summary>
    /// Necessary for EF core to be able to find and construct
    /// the DB context.
    /// </summary>
    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host.CreateDefaultBuilder(args)
            // Configure Application services
            .ConfigureServices((context, services) =>
            {
                ConfigureServices(context, services);
            });
    }

    /// <summary>
    /// Not necessary but I prefer having a separate function for
    /// configuring services.
    /// </summary>
    private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
    {
        ...
    }

    /// <summary>
    /// Hold the built Host for starting and stopping
    /// </summary>
    private readonly IHost AppHost;

    /// <summary>
    /// Constructor
    /// </summary>
    public App()
    {
        // Create Application host
        AppHost = CreateHostBuilder(new string[] { }).Build();
    }

    /// <summary>
    /// App Startup Event Handler
    /// </summary>
    private async void Application_Startup(object sender, StartupEventArgs e)
    {
        // Start the application host
        await AppHost.StartAsync();

        ...
    }

    /// <summary>
    /// App Exit Event Handler
    /// </summary>
    private async void Application_Exit(object sender, ExitEventArgs e)
    {
        // Kill the application host gracefully
        await AppHost.StopAsync(TimeSpan.FromSeconds(5));
        // Dispose of the host at the end of execution
        AppHost.Dispose();
    }
}
0
votes

You need to implement IDesignTimeDbContextFactory. There is a lot of hidden plumbing in an ASP.NET Core app that deals with wiring up the apps service provider so it can be found by the dotnet ef tooling, but no such plumbing exists in WPF. In addition the ef tools know nothing about WPF events so your OnStartup method isn't going to even be called (an instance the class wont even get created) to create your DI setup so that the ef tools can find your DBContext.

Move the code that creates the ServiceProvider into the constructor, other than the bit that looks up the main window and displays it.

Implement IDesignTimeDbContextFactory<ApplicationDbContext>. In the implemented CreateDbContext method return ServiceProvider.GetRequiredService<ApplicationDbContext>()

The tooling will then create an instance of your class, which will setup DI, and call that method to get your (now configured) DB Context and it should work.

I'd also recommend moving to HostBuilder based config (that blog post was written before the final version of Core 3 was released). You will find an updated version of the same post here