After migrating my project from EF Core/Asp.NET Core 1.1.2 to 2.0.0-preview2-latest in order to make use of the custom database function support, the migration engine will no longer work. I already updated the CLI tools to 2.0.0-preview2-final but it didn't fix the issue.
Here's all my Startup.cs and Context code for reference:
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(); }
}
Console output:
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'.
After being pointed to the note by Smit, here is the proper solution to the issue:
Due to a change in Asp.NET Core 2, the migrations tool can no longer simply use the Startup class as is. Instead, modify your Program.cs to something like this:
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();
}
After doing this, the migrations tool should be able to instantiate your context again, and you won't have to hardcode a design time factory.
For EF Core 2.0 RTM, Add this class to the same project where your context exists:
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);
}
}