EF Core迁移错误: 数据库 'MyDatabaseName' 已经存在。请选择一个不同的数据库名。

7

我正在运行一个基于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,启动应用程序。但是,如果我还原数据库,则会出现“已存在”的错误。

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

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

完整的错误信息:

应用程序启动异常:System.Data.SqlClient.SqlException (0x80131904):数据库'MyDatabaseName'已经存在。选择一个不同的数据库名称。 在 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) 中 在 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) 中 在 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) 中 在 System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) 中 在 System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite) 中 在 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName) 中 在 System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 中 在 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary2 parameterValues) 中 在 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary2 parameterValues) 中 在 Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary2 parameterValues) 中 在 Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection) 中 在 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerDatabaseCreator.Create() 中 在 Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration) 中 在 Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade) 中 在 MyProject.Startup.Configure(IApplicationBuilder app, AppUserManager userManager, IServiceProvider serviceProvider) 中,位于 C:\GitLab-Runner\builds\7cab42e4\0\web\MyProject\Startup.cs 的第582行 --- 前一个位置跟踪到异常被抛出的位置 --- 在 Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app) 中 在 Microsoft.AspNetCore.Server.IISIntegration.IISSetupFilter.<>c__DisplayClass4_0.b__0(IApplicationBuilder app) 中 在 Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.b__0(IApplicationBuilder builder) 中 在 Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() 中 客户端连接 ID: 7f6b84a3-e0ea-42c7-947d-a9cafdaffbfa 错误号码: 1801,状态: 3,类别: 16 托管环境: 生产 内容根路径: C:\WWW\MyProject 现在正在侦听: http://127.0.0.1:24830 应用程序已启动。按Ctrl+C关闭。应用程序正在关闭...

错误很简单。context.Database.Migrate(); 创建数据库。 - Vivek Nuna
为什么在开发环境下它能正常工作?如果我输入 MyDatabaseNameX 然后重新启动 IIS,为什么它会运行正常?当我运行应用程序时,它应该产生相同的错误,但它并没有。只有在使用还原时才会发生错误。在我开发时,每次调试都会进行此调用,但不会引发任何错误。 - Makla
你能在回滚之前删除 __efmigrationhistory 表,然后再尝试吗? - Vivek Nuna
1
连接字符串中使用的账户是否具有对恢复的数据库的访问权限? - Brad
4个回答

11
这是一个棘手的问题。数据库确实存在(我已经恢复了它),但问题在于备份所有者也被转移了。
在服务器上,本地主机所有者不存在。因此,迁移无法找到数据库(因为它没有访问权限),所以尝试创建一个新的数据库。

1
在开始讨论可能的修复方法之前,有一件重要的事情需要我们理解:迁移模式是确保您正在使用(并将用于连接应用程序的)所有数据库在任何给定环境(测试、阶段、生产、DR等)中具有一致且最新结构的绝佳方式;如果您选择使用它,最好的做法是坚持遵循最佳实践,并确保在需要时调用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()方法:这对于测试环境非常理想,您可以随时删除并重新创建数据库,而无需担心丢失实际数据,或者在任何其他情况下,您更喜欢手动更新您的数据库-例如使用dotnet ef powershell命令。这有利于性能,并避免了Migrate()方法中的SqlException,因为它只会在没有数据库的情况下运行,从而防止找到错误或过时的迁移数据的机会。
如果您对Exists()方法的实际操作感到好奇,可以查看SqlServer.Storage/Internal/SqlServerDatabaseCreator.cs类中的代码,该类位于EF Core官方GitHub存储库中:您将会发现,这里没有任何魔法 - 只是尝试打开连接并捕获SqlException并返回false或返回true。也许这不是您希望在那里找到的最好的东西,但仍然比没有强(至少它完成了工作)。
如果您需要其他信息,请查看我在此问题上撰写的博客文章

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

0

可能的情况是数据库正在被其他东西/早期迁移创建。

您可以通过在 Sql server management studio 中查看是否在列表中(不应该),来验证是否是这种情况。然后尝试使用相同的名称创建数据库。如果再次出现错误,则是因为已经在创建。

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接