EF Core Migration error: Database 'MyDatabaseName' already exists. Choose a different database name

EF Core Migration error: Database 'MyDatabaseName' already exists. Choose a different database name

我是 运行 ASP.NET Core 2.1 和 EF Core 2.1 应用程序,在 Windows Server 2016 和 SQL Server 2017 Web 版上。

Startup.cs 中的 public void Configure(IApplicationBuilder app, ... 方法结束时,我调用了 context.Database.Migrate();。这适用于迁移。
一切正常。

现在我在 SQL Server 2016 的开发环境中备份数据库,将 MyDatabaseName .bak 文件移动到服务器并在服务器上恢复数据库 MyDatabaseName 并重新启动 IIS 站点.
当我启动应用程序(打开浏览器)时出现以下错误:

Application startup exception: System.Data.SqlClient.SqlException (0x80131904): Database 'MyDatabaseName' already exists. Choose a different database name.

行内:context.Database.Migrate();。 完整错误在底部。

如果我将 MyDatabaseName 更改为 MyDatabaseNameX(不存在),则创建数据库,应用所有迁移,我可以重置 IIS,启动应用程序。如果我恢复数据库,我会收到错误 already exists.

相同的应用程序(完全相同的dll)在开发和生产环境中运行应用程序。这也意味着数据库结构是相同的。

我需要在生产环境中恢复数据库。我只是不确定为什么 context.Database.Migrate() 会抛出错误?

完整错误:

Application startup exception: System.Data.SqlClient.SqlException (0x80131904): Database 'MyDatabaseName' already exists. Choose a different database name. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName) at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerDatabaseCreator.Create() at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration) at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade) at MyProject.Startup.Configure(IApplicationBuilder app, AppUserManager userManager, IServiceProvider serviceProvider) in C:\GitLab-Runner\buildscab42e4[=25=]\web\MyProject\Startup.cs:line 582 --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app) at Microsoft.AspNetCore.Server.IISIntegration.IISSetupFilter.<>c__DisplayClass4_0.b__0(IApplicationBuilder app) at Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.b__0(IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() ClientConnectionId:7f6b84a3-e0ea-42c7-947d-a9cafdaffbfa Error Number:1801,State:3,Class:16 Hosting environment: Production Content root path: C:\WWW\MyProject Now listening on: http://127.0.0.1:24830 Application started. Press Ctrl+C to shut down. Application is shutting down...

可能是数据库是由某些 else/earlier 迁移创建的。

您可以通过在 Sql server management studio 中查看数据库是否在列表中(不应该)来验证是否属于这种情况。然后尝试创建同名数据库。如果您再次遇到错误,那是因为它已经被创建了。

至于解决方案,您可以简单地等到数据库存在,这样您就可以再次迁移,但这并不总是可行的。 另一种方法是尝试捕获此异常并添加重试机制。

try 
{
    // migrate
}
catch (SqlException exception) when (exception.Number == 1801)
{
    // retry
}

这是一个令人讨厌的人。数据库确实存在(我确实恢复了它),但问题是数据库的备份所有者也被转移了。
服务器上不存在本地主机所有者的用户。所以迁移没有找到数据库(因为它没有访问权限)所以它试图创建一个新的。

在开始讨论可能的修复之前,我们需要了解一件重要的事情:迁移模式是确保您正在处理的所有数据库(并且您将用于连接您的app with) 将在任何给定环境(测试、阶段、生产、灾难恢复等)中具有一致且最新的结构;如果您选择使用它,您能做的最好的事情就是坚持模式最佳实践并确保在需要时调用 Migrate() 方法。

即是说,您可能仍然希望仅使用 Migrate() 方法在第一个 运行 上创建您的数据库,而不必以编程方式(和自动)跟踪任何进一步的迁移。如果那是你生活的场景,你能做的最好的事情就是将 Migrate() 方法包装在一个条件块中,例如下面的块:

if (!dbContext.Database.GetService<IRelationalDatabaseCreator>().Exists())
{
    var host = BuildWebHost(args);
    using (var scope = host.Services.CreateScope())
    {
        var dbContext = scope.ServiceProvider.GetService<ApplicationDbContext>();
        var roleManager = scope.ServiceProvider.GetService<RoleManager<IdentityRole>>();
        var userManager = scope.ServiceProvider.GetService<UserManager<ApplicationUser>>();

        // Create the Db if it doesn't exist and applies any pending migration.
        dbContext.Database.Migrate();

        // Seed the Db
        DbSeeder.Seed(dbContext, roleManager, userManager);
    }
    host.Run();
}

这样您将确保 Migrate() 方法仅在数据库尚不存在时才以编程方式执行:这对于测试环境来说是完美的,您每次都可以删除并重新创建数据库无需担心丢失实际数据,and/or 对于您希望手动更新数据库的任何其他上下文——例如使用 dotnet ef powershell 命令。这对性能有好处,也可以避免 Migrate() 方法中的 SqlException,因为只要没有数据库开始,它只会 运行,从而防止发现错误或过时的机会迁移数据。

如果您对 Exists() 方法在幕后的实际作用感到好奇,您可以通过查看 SqlServer.Storage/Internal/SqlServerDatabaseCreator.cs class within the EF Core’s official GitHub repository 中的魔法——只是尝试打开连接并捕获 SqlException 和 return false 或 return true。可能不是您希望在那里找到的最好的东西,但总比没有好(至少它可以完成工作)。

如果您需要更多信息,请查看 the blog post that I wrote on such issue

IIS APPPOOL 用户添加到数据库的用户列表中 MyDatabaseName