简体   繁体   中英

Established dependency injection in .NET Core Console Application for EF Core DbContext Service Error

I've created a .net Core Console Application, and I've added the following dependency injection package:

Microsoft.Extensions.DependencyInjection

To inject EF Core DbContext Service, Here's a code snippet of the project:

static void Main(string[] args)
        {
            // Create service collection and configure our services
            var services = ConfigureServices();

            // Generate a provider
            var serviceProvider = services.BuildServiceProvider();

            // Kick off our actual code
            serviceProvider.GetService<Startup>().Run();
        }

        public static IConfiguration LoadConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .AddEnvironmentVariables();

            return builder.Build();
        }


private static IServiceCollection ConfigureServices()
        {
            IServiceCollection services = new ServiceCollection();

            // Set up the objects we need to get to configuration settings
            var configuration = LoadConfiguration();


            // IMPORTANT! Register our cvonfig file, db connection string, and application entry point(startup)
            services
                .AddSingleton(configuration)
                .AddConnection(configuration)
                .AddStartup();
            
            return services;
        }

Services Pool CLass:

  public static class ServicesPool
    {
        public static IServiceCollection AddStartup(this IServiceCollection services)
        {
            services.AddTransient<Startup>();

            return services;
        }

        public static IServiceCollection AddConfiggguration(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddSingleton(configuration);

            return services;
        }

        public static IServiceCollection AddConnection(this IServiceCollection services, IConfiguration configuration)
        {
            var connection = configuration.GetConnectionString("DEV_CS");
            services.AddDbContext<MigrationDbContext>(options =>
            options.UseSqlServer(connection, b => b.MigrationsAssembly("migration.presentence")));

            return services;
        }

    }

MigrationDbContext Service that implements the DbContext from EntityFramwork Core:

public class MigrationDbContext : DbContext
    {
        public MigrationDbContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
        {
        }

        public DbSet<RootItemMigrationEntity> RootItems { get; set; }

    }

The application runs without any problem, but when i try to create the inital migration using the following command:

Add-Migration 'Initial'

An error occurred with the following message:

Unable to create an object of type 'MigrationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

I've been investigating the problem, and I've tried multiple solutions but I wasn't able to solve it yet. So I'm going to share the stack trace of the error hoping to get help from you!

Stack Trace:

PM> Add-Migration 'Initial' -verbos Using project 'migration.presentence'. Using startup project 'e-commerce.migration'. Build started... Build succeeded. C:\Program Files\dotnet\dotnet.exe exec --depsfile C:\dev\backlogheros\e-commerce.migration\e-commerce.migration\bin\Debug\netcoreapp5.0\e-commerce.migration.deps.json --additionalprobingpath C:\Users\yousi.nuget\packages --runtimeconfig C:\dev\backlogheros\e-commerce.migration\e-commerce.migration\bin\Debug\netcoreapp5.0\e-commerce.migration.runtimeconfig.json C:\Users\yousi.nuget\packages\microsoft.entityframeworkcore.tools\5.0.1\tools\netcoreapp2.0\any\ef.dll migrations add Initial --json --verbose --no-color --prefix-output --assembly C:\dev\backlogheros\e-commerce.migration\e-commerce.migration\bin\Debug\netcoreapp5.0\migration.presentence.dll --startup-assembly Z0D6 1F8370CAD1D412F80B84D143E1257Z:\dev\backlogheros\e-commerce.migration\e-commerce.migration\bin\Debug\netcoreapp5.0\e-commerce.migration.dll --project-dir C:\dev\backlogheros\e-commerce.migration\migration.presentence
--language C# --working-dir C:\dev\backlogheros\e-commerce.migration --root-namespace migration.presentence Using assembly 'migration.presentence'. Using startup assembly 'e-commerce.migration'. Using application base 'C:\dev\backlogheros\e-commerce.migration\e-commerce.migration\bin\Debug\netcoreapp5.0'. Using working directory 'C:\dev\backlogheros\e-commerce.migration\e-commerce.migration'. Using root namespace 'migration.presentence'. Using project directory 'C:\dev\backlogheros\e-commerce.migration\migration.presentence'. Remaining arguments: . Finding DbContext classes... Finding IDesignTimeDbContextFactory implementations... Finding application service provider in assembly 'e-commerce.migration'... Finding Microsoft.Extensions.Hosting service provider... No static method 'CreateHostBuilder(string[])' was found on class 'Program'. No application service provider was found. Finding DbContext classes in the project... Found DbContext 'MigrationDbContext'. Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create an object of type 'MigrationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 ---> System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions' while attempting to activate 'migration.presentence.MigrationDbContext'.

at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)   
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)    
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider provider, Type type)    
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.<>c__DisplayClass13_4.<FindContextTypes>b__13()
--- End of inner exception stack trace ---    
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.<>c__DisplayClass13_4.<FindContextTypes>b__13()
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)    
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)    
at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)    
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)    
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)

The migration tool must instantiate your DbContext in order to create the migration. It has 3 ways of doing that:

  1. Resolve from the service provider
  2. new()
  3. IDesignTimeDbContextFactory

Resolve from service provider

If you want the migration tool to user the application configuration and resolve the DbContext from the service provider, there are multiple requirements as stated on page https://go.microsoft.com/fwlink/?linkid=851728 .

First, you need to have a constructor that takes a DbContextOptions on your DbContext. That part is ok, you need to specify DbContextOptions<TContext> only if you have multiple DbContext in your assembly.

Second, you must use a host builder (either ASP.NET, HTTP or Generic) and the tool will look for a public static CreateHostBuilder(string[] args) method in class Program to access it. This has a lot of side effects on your infrastructure classes since the IHostBuilder will do for you most of the ServiceCollection , and ConfigurationBuilder stuff.

Also, you DbContext needs to register as a singleton which can be a problem.

Program.cs

internal class Program
{
    public static void LoadConfiguration(HostBuilderContext host, IConfigurationBuilder builder)
    {
        builder
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
            .AddEnvironmentVariables();
    }

    private static void ConfigureServices(HostBuilderContext host, IServiceCollection services)
    {
        services
            .AddDbContext<MigrationDbContext>(options =>
            {
                options.UseSqlServer(
                    host.Configuration.GetConnectionString("DEV_CS"), builder =>
                        builder.MigrationsAssembly("migration.presentence"));
            }, ServiceLifetime.Singleton)
            .AddHostedService<Startup>();
    }

    private static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration(LoadConfiguration)
            .ConfigureServices(ConfigureServices);

    private static async Task Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
}

In order to make your host builder run your custom code, you will also need to turn the Startup class into a IHostedService such as BackgroundService for instance.

Startup.cs

internal class Startup : BackgroundService
{
    private readonly MigrationDbContext context;

    public Startup(MigrationDbContext context)
    {
        this.context = context ?? throw new ArgumentNullException(nameof(context));
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        throw new NotImplementedException();
    }
}

new()

Another option is to use a default construction on your DbContext. However, in that case you can't have both the default constructor and the DbContextOptions constructor simultaneously. Sad.

IDesignTimeDbContextFactory

The easiest solution in your situation is probably to implement IDesignTimeDbContextFactory<TContext> . This class will be used by the tools if it can't find the IHostBuilder and the DbContext uses the DbContextOptions constructor. Implementation is super simple:

internal class MigrationDbContextFactory : IDesignTimeDbContextFactory<MigrationDbContext>
{
    public MigrationDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<MigrationDbContext>();
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Test",
            b => b.MigrationsAssembly("migration.presentence"));
        return new MigrationDbContext(optionsBuilder.Options);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM