Docker容器重启后ASP.NET Core的Database.Migrate执行失败
我将ASP.NET Core Web API部署在Docker中,应用与容器内的SQL Server进行交互。docker-compose.yml配置如下:
version: '3' services: db: image: mcr.microsoft.com/mssql/server container_name: db ports: - 1533:1433 environment: - TZ=Asia/Tehran - ACCEPT_EULA=Y - MSSQL_SA_PASSWORD=****** user: root volumes: - ./mssql:/var/opt/mssql/data kava-service: image: service:1.0.0 container_name: service expose: - 9001 ports: - 9001:9001 depends_on: - db environment: - ASPNETCORE__ENVIRONMENT=Production - TZ=Asia/Tehran - AUTO__MIGRATE=true - ConnectionStrings__BusinessDomainConnectionString=Server=db;Initial Catalog=Kava.BusinessDomain;User ID=sa;pwd=****;MultipleActiveResultSets=true; - ConnectionStrings__RecordsConnectionString=Server=db;Initial Catalog=Kava.Records;User Id=sa;pwd=****;MultipleActiveResultSets=true;
首次通过docker-compose启动容器运行正常,但重启容器后应用退出,报错信息:
Login failed for user 'sa'. Reason: Failed to open the explicitly specified database 'Kava.BusinessDomain' [CLIENT: 172.18.0.4] kava-service
Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904): Database 'Kava.BusinessDomain' already exists. Choose a different database name.
删除数据库后可恢复正常,但我希望保留数据库。开发环境(Visual Studio)中一切正常,但Docker生产环境的数据迁移不符合预期。迁移代码如下:
BusinessDomainContext businessDomainContext = serviceScope.ServiceProvider.GetRequiredService<IBusinessDomainDatabaseFactory>().Get() as BusinessDomainContext; await businessDomainContext.Database.MigrateAsync();
解决方案
1. 修复SQL Server容器的用户权限问题
SQL Server官方镜像默认要求以mssql用户运行,配置user: root会导致挂载的数据目录权限异常,重启后数据库文件无法正常加载,引发"找不到数据库"和"数据库已存在"的冲突报错。
修改db服务配置,移除user: root:
db: image: mcr.microsoft.com/mssql/server container_name: db ports: - 1533:1433 environment: - TZ=Asia/Tehran - ACCEPT_EULA=Y - MSSQL_SA_PASSWORD=****** volumes: - ./mssql:/var/opt/mssql/data
如果之前已用root运行过容器,需重置挂载目录权限:
# 容器内调整目录权限 docker exec -it db chown -R mssql:mssql /var/opt/mssql/data # 或本地目录权限调整 sudo chown -R 10001:10001 ./mssql
2. 确保应用在SQL Server完全就绪后再启动
depends_on仅保证db容器启动,不确保SQL Server服务完全就绪。重启时应用可能在数据库未加载完成时就尝试连接,后续迁移逻辑又触发创建已存在数据库的冲突。
在应用启动时添加连接检测逻辑,直到SQL Server就绪再执行迁移:
var host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { // 注册你的服务依赖 }) .Build(); // 检测SQL Server连接可用性 using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var logger = services.GetRequiredService<ILogger<Program>>(); var retryCount = 0; const int maxRetries = 10; while (retryCount < maxRetries) { try { var context = services.GetRequiredService<IBusinessDomainDatabaseFactory>().Get() as BusinessDomainContext; await context.Database.OpenConnectionAsync(); logger.LogInformation("SQL Server连接成功"); await context.Database.CloseConnectionAsync(); break; } catch (Exception ex) { retryCount++; logger.LogWarning(ex, $"连接SQL Server失败,重试第{retryCount}次"); await Task.Delay(2000); if (retryCount == maxRetries) { logger.LogCritical("重试多次后仍无法连接SQL Server,退出应用"); throw; } } } } // 执行数据迁移 using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var businessDomainContext = services.GetRequiredService<IBusinessDomainDatabaseFactory>().Get() as BusinessDomainContext; await businessDomainContext.Database.MigrateAsync(); } await host.RunAsync();
3. 清理迁移逻辑中的冲突代码
检查DbContext相关代码,确保没有同时使用EnsureCreatedAsync()和MigrateAsync()。EnsureCreatedAsync()会直接创建数据库但不应用迁移,和MigrateAsync()混用会导致"数据库已存在"的冲突,只保留MigrateAsync()即可。
内容的提问来源于stack exchange,提问作者RezaNoei

