使用 Entity Framework 核心迁移命令时无法附加数据库文件
Cannot attach database file when using Entity Framework Core Migration commands
我正在使用 EntityFramework 核心命令来迁移数据库。我使用的命令就像文档建议的那样: dnx 。 ef 迁移适用。问题是在连接字符串中指定 AttachDbFileName 时,出现以下错误:无法将数据库文件附加为数据库 xxxxxxx。这是我正在使用的连接字符串:
数据源=(LocalDB)\mssqllocaldb;集成安全性=True;初始目录=EfGetStarted2;AttachDbFileName=D:\EfGetStarted2.mdf
请帮助如何将 db 文件附加到另一个位置。
谢谢
可能有一个不同的 *.mdf 文件已附加到名为 EfGetStarted2 的数据库...请尝试 dropping/detaching 该数据库,然后重试。
如果用户 LocalDB 运行 没有正确的路径权限,您可能也会 运行 遇到问题。
EF 核心似乎在使用 AttachDbFileName 时遇到问题或根本无法处理它。
EnsureDeleted
将数据库名称更改为 master 但保留任何 AttachDbFileName 值,这会导致错误,因为我们不能将 master 数据库附加到另一个文件。
EnsureCreated
使用提供的 AttachDbFileName 值打开连接,这会导致错误,因为我们要创建的数据库文件尚不存在。
EF6 有一些逻辑来处理这些用例,请参阅 SqlProviderServices.DbCreateDatabase
,因此一切正常。
作为解决方法,我编写了一些 hacky 代码来处理这些情况:
public static void EnsureDatabase(this DbContext context, bool reset = false)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (reset)
{
try
{
context.Database.EnsureDeleted();
}
catch (SqlException ex) when (ex.Number == 1801)
{
// HACK: EF doesn't interpret error 1801 as already existing database
ExecuteStatement(context, BuildDropStatement);
}
catch (SqlException ex) when (ex.Number == 1832)
{
// nothing to do here (see below)
}
}
try
{
context.Database.EnsureCreated();
}
catch (SqlException ex) when (ex.Number == 1832)
{
// HACK: EF doesn't interpret error 1832 as non existing database
ExecuteStatement(context, BuildCreateStatement);
// this takes some time (?)
WaitDatabaseCreated(context);
// re-ensure create for tables and stuff
context.Database.EnsureCreated();
}
}
private static void WaitDatabaseCreated(DbContext context)
{
var timeout = DateTime.UtcNow + TimeSpan.FromMinutes(1);
while (true)
{
try
{
context.Database.OpenConnection();
context.Database.CloseConnection();
}
catch (SqlException)
{
if (DateTime.UtcNow > timeout)
throw;
continue;
}
break;
}
}
private static void ExecuteStatement(DbContext context, Func<SqlConnectionStringBuilder, string> statement)
{
var builder = new SqlConnectionStringBuilder(context.Database.GetDbConnection().ConnectionString);
using (var connection = new SqlConnection($"Data Source={builder.DataSource}"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = statement(builder);
command.ExecuteNonQuery();
}
}
}
private static string BuildDropStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
return $"drop database [{database}]";
}
private static string BuildCreateStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
var datafile = builder.AttachDBFilename;
var dataname = Path.GetFileNameWithoutExtension(datafile);
var logfile = Path.ChangeExtension(datafile, ".ldf");
var logname = dataname + "_log";
return $"create database [{database}] on primary (name = '{dataname}', filename = '{datafile}') log on (name = '{logname}', filename = '{logfile}')";
}
它远谈不上好,但我还是用它来进行集成测试。对于 "real world" 使用 EF 迁移的场景应该是可行的方法,但也许这个问题的根本原因是相同的...
更新
下一版本将包括 support for AttachDBFilename。
我正在使用 EntityFramework 核心命令来迁移数据库。我使用的命令就像文档建议的那样: dnx 。 ef 迁移适用。问题是在连接字符串中指定 AttachDbFileName 时,出现以下错误:无法将数据库文件附加为数据库 xxxxxxx。这是我正在使用的连接字符串: 数据源=(LocalDB)\mssqllocaldb;集成安全性=True;初始目录=EfGetStarted2;AttachDbFileName=D:\EfGetStarted2.mdf
请帮助如何将 db 文件附加到另一个位置。 谢谢
可能有一个不同的 *.mdf 文件已附加到名为 EfGetStarted2 的数据库...请尝试 dropping/detaching 该数据库,然后重试。
如果用户 LocalDB 运行 没有正确的路径权限,您可能也会 运行 遇到问题。
EF 核心似乎在使用 AttachDbFileName 时遇到问题或根本无法处理它。
EnsureDeleted
将数据库名称更改为 master 但保留任何 AttachDbFileName 值,这会导致错误,因为我们不能将 master 数据库附加到另一个文件。EnsureCreated
使用提供的 AttachDbFileName 值打开连接,这会导致错误,因为我们要创建的数据库文件尚不存在。
EF6 有一些逻辑来处理这些用例,请参阅 SqlProviderServices.DbCreateDatabase
,因此一切正常。
作为解决方法,我编写了一些 hacky 代码来处理这些情况:
public static void EnsureDatabase(this DbContext context, bool reset = false)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (reset)
{
try
{
context.Database.EnsureDeleted();
}
catch (SqlException ex) when (ex.Number == 1801)
{
// HACK: EF doesn't interpret error 1801 as already existing database
ExecuteStatement(context, BuildDropStatement);
}
catch (SqlException ex) when (ex.Number == 1832)
{
// nothing to do here (see below)
}
}
try
{
context.Database.EnsureCreated();
}
catch (SqlException ex) when (ex.Number == 1832)
{
// HACK: EF doesn't interpret error 1832 as non existing database
ExecuteStatement(context, BuildCreateStatement);
// this takes some time (?)
WaitDatabaseCreated(context);
// re-ensure create for tables and stuff
context.Database.EnsureCreated();
}
}
private static void WaitDatabaseCreated(DbContext context)
{
var timeout = DateTime.UtcNow + TimeSpan.FromMinutes(1);
while (true)
{
try
{
context.Database.OpenConnection();
context.Database.CloseConnection();
}
catch (SqlException)
{
if (DateTime.UtcNow > timeout)
throw;
continue;
}
break;
}
}
private static void ExecuteStatement(DbContext context, Func<SqlConnectionStringBuilder, string> statement)
{
var builder = new SqlConnectionStringBuilder(context.Database.GetDbConnection().ConnectionString);
using (var connection = new SqlConnection($"Data Source={builder.DataSource}"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = statement(builder);
command.ExecuteNonQuery();
}
}
}
private static string BuildDropStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
return $"drop database [{database}]";
}
private static string BuildCreateStatement(SqlConnectionStringBuilder builder)
{
var database = builder.InitialCatalog;
var datafile = builder.AttachDBFilename;
var dataname = Path.GetFileNameWithoutExtension(datafile);
var logfile = Path.ChangeExtension(datafile, ".ldf");
var logname = dataname + "_log";
return $"create database [{database}] on primary (name = '{dataname}', filename = '{datafile}') log on (name = '{logname}', filename = '{logfile}')";
}
它远谈不上好,但我还是用它来进行集成测试。对于 "real world" 使用 EF 迁移的场景应该是可行的方法,但也许这个问题的根本原因是相同的...
更新
下一版本将包括 support for AttachDBFilename。