在 Entity Framework 中重复创建和删除数据库
Repeatedly creating and deleting databases in Entity Framework
在为我们的应用程序编写一些单元测试时,我偶然发现了 EF6 中的一些奇怪行为(使用 6.1 和 6.1.2 进行了测试):显然不可能重复创建和删除数据库(相同的 name/same 连接字符串)在同一应用程序上下文中。
测试设置:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
HasKey(a => a.Id);
Property(a => a.Name).IsRequired().IsMaxLength().HasColumnName("Name");
Property(a => a.Id).HasColumnName("ID");
}
}
public class SomeContext : DbContext
{
public SomeContext(DbConnection connection, bool ownsConnection) : base(connection, ownsConnection)
{
}
public DbSet<A> As { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new AMap());
}
}
[TestFixture]
public class BasicTest
{
private readonly HashSet<string> m_databases = new HashSet<string>();
#region SetUp/TearDown
[TestFixtureSetUp]
public void SetUp()
{
System.Data.Entity.Database.SetInitializer(
new CreateDatabaseIfNotExists<SomeContext>());
}
[TestFixtureTearDown]
public void TearDown()
{
foreach (var database in m_databases)
{
if (!string.IsNullOrWhiteSpace(database))
DeleteDatabase(database);
}
}
#endregion
[Test]
public void RepeatedCreateDeleteSameName()
{
var dbName = Guid.NewGuid().ToString();
m_databases.Add(dbName);
for (int i = 0; i < 2; i++)
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteDifferentName()
{
for (int i = 0; i < 2; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteReuseName()
{
var testDatabases = new HashSet<string>();
for (int i = 0; i < 3; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
testDatabases.Add(dbName);
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
var repeatName = testDatabases.OrderBy(n => n).FirstOrDefault();
Assert.IsTrue(CreateDatabase(repeatName), "failed to create database");
Assert.IsTrue(DeleteDatabase(repeatName), "failed to delete database");
Console.WriteLine();
}
#region Helpers
private static bool CreateDatabase(string databaseName)
{
Console.Write("creating database '" + databaseName + "'...");
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
using (var context = new SomeContext(connection, false))
{
var a = context.As.ToList(); // CompatibleWithModel must not be the first call
var result = context.Database.CompatibleWithModel(false);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
}
}
private static bool DeleteDatabase(string databaseName)
{
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
if (System.Data.Entity.Database.Exists(connection))
{
Console.Write("deleting database '" + databaseName + "'...");
var result = System.Data.Entity.Database.Delete(connection);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
return true;
}
}
private static DbConnection CreateConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
private static string CreateConnectionString(string databaseName)
{
var builder = new SqlConnectionStringBuilder
{
DataSource = "server",
InitialCatalog = databaseName,
IntegratedSecurity = false,
MultipleActiveResultSets = false,
PersistSecurityInfo = true,
UserID = "username",
Password = "password"
};
return builder.ConnectionString;
}
#endregion
}
RepeatedCreateDeleteDifferentName 成功完成,其他两个失败。根据这一点,您不能创建一个以前已经使用过一次的同名数据库。第二次尝试创建数据库时,测试(和应用程序)抛出 SqlException,指出登录失败。这是 Entity Framework 中的错误还是这种行为是故意的(有什么解释)?
我在 Ms SqlServer 2012 和 Express 2014 上测试过这个,还没有在 Oracle 上测试过。
顺便说一下:EF 似乎有一个问题,CompatibleWithModel 是对数据库的第一次调用。
更新:
在 EF 错误跟踪器上提交了一个问题 (link)
几天前,我编写了集成测试,其中包括通过 EF6 访问数据库。为此,我必须在每个测试用例上创建和删除一个 LocalDB 数据库,它对我有用。
我没有使用 EF6 数据库初始化程序功能,而是在 this post 的帮助下执行了 DROP/CREATE 数据库脚本 - 我在此处复制了示例:
using (var conn = new SqlConnection(@"Data Source=(LocalDb)\v11.0;Initial Catalog=Master;Integrated Security=True"))
{
conn.Open();
var cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = string.Format(@"
IF EXISTS(SELECT * FROM sys.databases WHERE name='{0}')
BEGIN
ALTER DATABASE [{0}]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
DROP DATABASE [{0}]
END
DECLARE @FILENAME AS VARCHAR(255)
SET @FILENAME = CONVERT(VARCHAR(255), SERVERPROPERTY('instancedefaultdatapath')) + '{0}';
EXEC ('CREATE DATABASE [{0}] ON PRIMARY
(NAME = [{0}],
FILENAME =''' + @FILENAME + ''',
SIZE = 25MB,
MAXSIZE = 50MB,
FILEGROWTH = 5MB )')",
databaseName);
cmd.ExecuteNonQuery();
}
以下代码负责根据模型创建数据库对象:
var script = objectContext.CreateDatabaseScript();
using ( var command = connection.CreateCommand() )
{
command.CommandType = CommandType.Text;
command.CommandText = script;
connection.Open();
command.ExecuteNonQuery();
}
测试之间无需更改数据库名称。
每个 AppDomain 的每个上下文仅 运行 一次数据库初始化程序。因此,如果您在任意点删除数据库,它们不会自动重新 运行 并重新创建数据库。您可以使用 DbContext.Database.Initialize(force: true)
强制初始化程序再次 运行。
在为我们的应用程序编写一些单元测试时,我偶然发现了 EF6 中的一些奇怪行为(使用 6.1 和 6.1.2 进行了测试):显然不可能重复创建和删除数据库(相同的 name/same 连接字符串)在同一应用程序上下文中。
测试设置:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
HasKey(a => a.Id);
Property(a => a.Name).IsRequired().IsMaxLength().HasColumnName("Name");
Property(a => a.Id).HasColumnName("ID");
}
}
public class SomeContext : DbContext
{
public SomeContext(DbConnection connection, bool ownsConnection) : base(connection, ownsConnection)
{
}
public DbSet<A> As { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new AMap());
}
}
[TestFixture]
public class BasicTest
{
private readonly HashSet<string> m_databases = new HashSet<string>();
#region SetUp/TearDown
[TestFixtureSetUp]
public void SetUp()
{
System.Data.Entity.Database.SetInitializer(
new CreateDatabaseIfNotExists<SomeContext>());
}
[TestFixtureTearDown]
public void TearDown()
{
foreach (var database in m_databases)
{
if (!string.IsNullOrWhiteSpace(database))
DeleteDatabase(database);
}
}
#endregion
[Test]
public void RepeatedCreateDeleteSameName()
{
var dbName = Guid.NewGuid().ToString();
m_databases.Add(dbName);
for (int i = 0; i < 2; i++)
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteDifferentName()
{
for (int i = 0; i < 2; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteReuseName()
{
var testDatabases = new HashSet<string>();
for (int i = 0; i < 3; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
testDatabases.Add(dbName);
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
var repeatName = testDatabases.OrderBy(n => n).FirstOrDefault();
Assert.IsTrue(CreateDatabase(repeatName), "failed to create database");
Assert.IsTrue(DeleteDatabase(repeatName), "failed to delete database");
Console.WriteLine();
}
#region Helpers
private static bool CreateDatabase(string databaseName)
{
Console.Write("creating database '" + databaseName + "'...");
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
using (var context = new SomeContext(connection, false))
{
var a = context.As.ToList(); // CompatibleWithModel must not be the first call
var result = context.Database.CompatibleWithModel(false);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
}
}
private static bool DeleteDatabase(string databaseName)
{
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
if (System.Data.Entity.Database.Exists(connection))
{
Console.Write("deleting database '" + databaseName + "'...");
var result = System.Data.Entity.Database.Delete(connection);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
return true;
}
}
private static DbConnection CreateConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
private static string CreateConnectionString(string databaseName)
{
var builder = new SqlConnectionStringBuilder
{
DataSource = "server",
InitialCatalog = databaseName,
IntegratedSecurity = false,
MultipleActiveResultSets = false,
PersistSecurityInfo = true,
UserID = "username",
Password = "password"
};
return builder.ConnectionString;
}
#endregion
}
RepeatedCreateDeleteDifferentName 成功完成,其他两个失败。根据这一点,您不能创建一个以前已经使用过一次的同名数据库。第二次尝试创建数据库时,测试(和应用程序)抛出 SqlException,指出登录失败。这是 Entity Framework 中的错误还是这种行为是故意的(有什么解释)?
我在 Ms SqlServer 2012 和 Express 2014 上测试过这个,还没有在 Oracle 上测试过。 顺便说一下:EF 似乎有一个问题,CompatibleWithModel 是对数据库的第一次调用。
更新: 在 EF 错误跟踪器上提交了一个问题 (link)
几天前,我编写了集成测试,其中包括通过 EF6 访问数据库。为此,我必须在每个测试用例上创建和删除一个 LocalDB 数据库,它对我有用。
我没有使用 EF6 数据库初始化程序功能,而是在 this post 的帮助下执行了 DROP/CREATE 数据库脚本 - 我在此处复制了示例:
using (var conn = new SqlConnection(@"Data Source=(LocalDb)\v11.0;Initial Catalog=Master;Integrated Security=True"))
{
conn.Open();
var cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = string.Format(@"
IF EXISTS(SELECT * FROM sys.databases WHERE name='{0}')
BEGIN
ALTER DATABASE [{0}]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
DROP DATABASE [{0}]
END
DECLARE @FILENAME AS VARCHAR(255)
SET @FILENAME = CONVERT(VARCHAR(255), SERVERPROPERTY('instancedefaultdatapath')) + '{0}';
EXEC ('CREATE DATABASE [{0}] ON PRIMARY
(NAME = [{0}],
FILENAME =''' + @FILENAME + ''',
SIZE = 25MB,
MAXSIZE = 50MB,
FILEGROWTH = 5MB )')",
databaseName);
cmd.ExecuteNonQuery();
}
以下代码负责根据模型创建数据库对象:
var script = objectContext.CreateDatabaseScript();
using ( var command = connection.CreateCommand() )
{
command.CommandType = CommandType.Text;
command.CommandText = script;
connection.Open();
command.ExecuteNonQuery();
}
测试之间无需更改数据库名称。
每个 AppDomain 的每个上下文仅 运行 一次数据库初始化程序。因此,如果您在任意点删除数据库,它们不会自动重新 运行 并重新创建数据库。您可以使用 DbContext.Database.Initialize(force: true)
强制初始化程序再次 运行。