在Entity Framework中重复创建和删除数据库

时间:2015-01-29 12:31:56

标签: c# .net sql-server database entity-framework-6

在为我们的应用程序编写一些单元测试时,我偶然发现了EF6中的一些奇怪的行为(使用6.1和6.1.2测试):显然不可能重复创建和删除数据库(同名/相同的连接字符串)相同的应用背景。

测试设置:

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,注意登录失败。这是实体框架中的错误还是故意这种行为(有什么解释)?

我在Ms SqlServer 2012和Express 2014上测试了这个,还没有在Oracle上测试过。 顺便说一下:EF似乎有一个问题,即CompatibleWithModel是对数据库的第一次调用。

更新: 在EF错误跟踪器(link

上提交了一个问题

2 个答案:

答案 0 :(得分:3)

数据库初始化程序每个AppDomain每个上下文只运行一次。因此,如果您在某个任意点删除数据库,则它们不会自动重新运行并重新创建数据库。您可以使用DbContext.Database.Initialize(force: true)强制初始化程序再次运行。

答案 1 :(得分:1)

几天前,我编写了集成测试,其中包括通过EF6进行数据库访问。为此,我必须在每个测试用例上创建和删除一个LocalDB数据库,它对我有用。

我没有使用EF6数据库初始化程序功能,而是在this post的帮助下执行了DROP / CREATE DATABASE脚本 - 我在这里复制了示例:

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();
}

无需在测试之间更改数据库名称。