迁移到 2.0.0-preview 后未找到无参数构造函数

No parameterless constructor found after migrating to 2.0.0-preview

将我的项目从 EF Core/Asp.NET Core 1.1.2 迁移到 2.0.0-preview2-latest 以使用自定义数据库功能支持后,迁移引擎将不再工作。我已经将 CLI 工具更新到 2.0.0-preview2-final 但它没有解决问题。

这是我所有的Startup.cs和上下文代码供参考:

Startup.cs:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

        //if (env.IsDevelopment()) builder.AddUserSecrets<Startup>();

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddLocalization(options => options.ResourcesPath = "Resources");
        services.AddOptions();
        services.Configure<Config>(Configuration.GetSection("AppSettings"));
        services.AddSingleton<IConfiguration>(Configuration);
        services.AddDbContext<MemoryContext>(
            options => options.UseSqlServer(Configuration.GetConnectionString("Database"), b => b.MigrationsAssembly("MemoryServer")));

        services.AddIdentity<User, IdentityRole<Guid>>()
            .AddEntityFrameworkStores<MemoryContext>()
            .AddDefaultTokenProviders();
        // Add framework services.
        services.AddMvc();

        services.ConfigureApplicationCookie(options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromDays(150);
            options.LoginPath = "/api/auth/login";
            options.LogoutPath = "/api/auth/logout";
        });
        services.Configure<IdentityOptions>(options =>
        {
            options.Password.RequireDigit = true;
            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = true;
            options.Password.RequireLowercase = true;

            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
            options.Lockout.MaxFailedAccessAttempts = 10;

            options.User.RequireUniqueEmail = true;
            options.SignIn.RequireConfirmedEmail = false;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
        };

        app.UseRequestLocalization(new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture("en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        });

        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
            app.UseDeveloperExceptionPage();
        else
            app.UseExceptionHandler("/Home/Error");

        app.UseAuthentication();

        app.UseMvcWithDefaultRoute();
    }
}

MemoryContext.cs:

public class MemoryContext : IdentityDbContext<User, IdentityRole<Guid>, Guid>
{
    public DbSet<Lesson> Lessons { get; set; }
    public DbSet<LessonAssignment> Assignments { get; set; }
    public DbSet<Review> Reviews { get; set; }
    public DbSet<UserList> UserLists { get; set; }
    public DbSet<Language> Languages { get; set; }

    public MemoryContext(DbContextOptions options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.Entity<UserListEntry>().HasKey(a => new {a.OwnerId, a.LessonId});
        builder.HasDbFunction(typeof(MemoryContext).GetMethod(nameof(Levenshtein)), funBuilder => {});
    }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        base.OnConfiguring(builder);
        builder.EnableSensitiveDataLogging();
    }

    public static int Levenshtein(string s1, string s2, int max) { throw new NotImplementedException(); }
}

控制台输出:

No parameterless constructor was found on 'MemoryContext'. Either add a parameterless constructor to 'MemoryContext' or add an implementation of 'IDesignTimeDbContextFactory' in the same assembly as 'MemoryContext'.

经 Smit 指点后,问题的正确解决方案如下:

由于 Asp.NET Core 2 的变化,迁移工具不能再简单地使用 Startup class。相反,将您的 Program.cs 修改为如下内容:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();

        host.Run();
    }

    public static IWebHost BuildWebHost(string[] args) => 
        new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();
}

执行此操作后,迁移工具应该能够再次实例化您的上下文,并且您不必对设计时工厂进行硬编码。

对于 EF Core 2.0 RTM,将此 class 添加到您的上下文所在的同一项目中:

public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
    public MyDbContext CreateDbContext(string[] args)
    {
        IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        var builder = new DbContextOptionsBuilder<MyDbContext>();
        var connectionString = configuration.GetConnectionString("DefaultConnection");
        builder.UseSqlServer(connectionString);
        return new MyDbContext(builder.Options);
    }
}