2

我已经在我的实体框架项目上启用了代码优先迁移,并添加了几个迁移来执行重命名表等操作。但是,我现在已经删除了数据库并导致实体框架根据我最新的数据模型生成一个全新的数据库。如果我尝试运行:

PM> Add-Migration TestMigration

...它告诉我我需要先应用现有的迁移。所以我跑:

PM> Update-Database

...但问题是它正在尝试更新不需要更新的数据库;它已经基于最新的数据模型。因此,当它尝试重命名现在不存在的表时出现错误。

有什么方法可以向数据迁移表明我的数据库是最新的并且不需要在其上运行任何迁移?我该怎么办?

4

3 回答 3

9

我找到了一种方法来表明我的数据库是最新的,并且它(不出所料)基于修改__MigrationHistory表,代码优先迁移用于确定在运行时将哪些迁移应用于数据库Update-Database

顺便说一句,在研究这个答案时,我发现了一个非常好的代码优先迁移命令参考,可以在以下位置找到:http ://dotnet.dzone.com/articles/ef-migrations-command

当 EF 自动从头开始创建数据库时,它将始终将单个条目放入__MigrationHistory表中,并且该条目将具有 MigrationId (currentDateTime)_InitialCreate。这表示 EF 刚刚执行的数据库的初始创建。但是,您的迁移历史不会以该 MigrationId 开始,因为您将从其他内容开始。

要“愚弄”代码优先迁移以为您正在进行最新迁移,您需要从新创建的数据库(currentDateTime)_InitialCreate的表中删除该条目__MigrationHistory,并插入如果您仍然拥有旧数据库的内容已经应用了迁移。

因此,首先从新生成的数据库__MigrationHistory表中删除所有内容。然后,进入包管理器控制台并运行:

PM> Update-Database -Script

从生成的 SQL 脚本中,拉出所有以以下开头的行:

INSERT INTO [__MigrationHistory]...

然后,INSERT在新创建的数据库的上下文中运行这些语句。检查这些行中的每一行现在是否存在于您的__MigrationHistory表中。下次运行时:

PM> Update-Database

...您应该会收到一条消息“没有待处理的基于代码的迁移”。恭喜 - 您已经欺骗了代码优先迁移,认为您现在正在进行最新迁移,您可以从此处继续添加新迁移。

我确实认为应该有一些自动化的方式来做这个内置到 EF 代码优先,虽然......也许他们应该添加类似的东西:

PM> Update-Database -MigrationsTableOnly

...它会破坏迁移表中的现有条目,并将新条目插入到项目中定义的每个迁移的迁移历史记录中,而不是实际尝试运行迁移。呃,好吧。

更新
我找到了一种很好的自动化方法,使用自定义初始化程序的 Seed 方法。基本上 Seed 方法会在创建数据库时删除现有的迁移历史数据,并插入您的迁移历史。在我的数据库上下文构造函数中,我像这样注册自定义初始化程序:

public class MyDatabaseContext : DbContext {
    public MyDatabaseContext() : base() {
        Database.SetInitializer(new MyDatabaseContextMigrationHistoryInitializer());
    }

自定义初始化程序本身如下所示:

/// <summary>
/// This initializer clears the __MigrationHistory table contents created by EF code-first when it first
/// generates the database, and inserts all the migration history entries for migrations that have been
/// created in this project, indicating to EF code-first data migrations that the database is
/// "up-to-date" and that no migrations need to be run when "Update-Database" is run, because we're
/// already at the latest schema by virtue of the fact that the database has just been created from
/// scratch using the latest schema.
/// 
/// The Seed method needs to be updated each time a new migration is added with "Add-Migration".  In
/// the package manager console, run "Update-Database -Script", and in the SQL script which is generated,
/// find the INSERT statement that inserts the row for that new migration into the __MigrationHistory
/// table.  Add that INSERT statement as a new "ExecuteSqlCommand" to the end of the Seed method.
/// </summary>
public class MyDatabaseContextMigrationHistoryInitializer : CreateDatabaseIfNotExists<MyDatabaseContext> {
    /// <summary>
    /// Sets up this context's migration history with the entries for all migrations that have been created in this project.
    /// </summary>
    /// <param name="context">The context of the database in which the seed code is to be executed.</param>
    protected override void Seed(MyDatabaseContext context) {
        // Delete existing content from migration history table, and insert our project's migrations
        context.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");
        context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210091606260_InitialCreate', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E074A..., '5.0.0.net40')");
        context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210102218467_MakeConceptUserIdNullable', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4..., '5.0.0.net40')");
        context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210231418163_ChangeDateTimesToDateTimeOffsets', 0x1F8B0800000000000400ECBD07601C499625262F6D..., '5.0.0.net40')");
        context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210251833252_AddConfigSettings', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E..., '5.0.0.net40')");
        context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210260822485_RenamingOfSomeEntities', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF5..., '5.0.0.net40')");
    }
}
于 2012-10-28T10:59:59.507 回答
0

转到MigrationHistorysql server 中的表(在system文件夹下),它有你的迁移行和 db 哈希,其中必须与迁移文件中的一个相同,只需将其从 db 复制到文件。

换句话说,您需要将MigrationHistory表与实际迁移同步。

于 2012-10-28T09:29:36.367 回答
0

此实现不需要手动维护要插入的记录__MigrationHistory。迁移是根据指定的程序集确定的。

也许这有帮助。

我感谢@Jez 的初步想法。

/// <summary>
/// An implementation of IDatabaseInitializer that will:
/// 1. recreate database only if the database does not exist 
/// 2. actualize __MigrationHistory to match current model state (i.e. latest migration)
/// </summary>
/// <typeparam name="TContext">The type of the context.</typeparam>
public class CreateDatabaseIfNotExistsAndMigrateToLatest<TContext> : CreateDatabaseIfNotExists<TContext>
    where TContext : DbContext
{
    private readonly Assembly migrationsAssembly;

    /// <summary>
    /// Gets the migration metadata for types retrieved from <paramref name="assembly"/>. Types must implement <see cref="IMigrationMetadata"/>.
    /// </summary>
    /// <param name="assembly">The assembly.</param>
    /// <returns></returns>
    private static IEnumerable<IMigrationMetadata> GetMigrationMetadata(Assembly assembly)
    {
        var types = assembly.GetTypes().Where(t => typeof(IMigrationMetadata).IsAssignableFrom(t));
        var migrationMetadata = new List<IMigrationMetadata>();
        foreach (var type in types)
        {
            migrationMetadata.Add(
                (IMigrationMetadata)Activator.CreateInstance(type));
        }

        return migrationMetadata.OrderBy(m => m.Id);
    }

    /// <summary>
    /// Gets the provider manifest token.
    /// </summary>
    /// <param name="db">The db.</param>
    /// <returns></returns>
    private static string GetProviderManifestToken(TContext db)
    {
        var connection = db.Database.Connection;
        var token = DbProviderServices.GetProviderServices(connection).GetProviderManifestToken(connection);

        return token;
    }

    /// <summary>
    /// Gets the migration SQL generator. Currently it is <see cref="SqlServerMigrationSqlGenerator"/>.
    /// </summary>
    /// <returns></returns>
    private static MigrationSqlGenerator GetMigrationSqlGenerator()
    {
        return new SqlServerMigrationSqlGenerator();
    }

    /// <summary>
    /// Creates the operation for inserting into migration history. Operation is created for one <paramref name="migrationMetadatum"/>.
    /// </summary>
    /// <param name="migrationMetadatum">The migration metadatum.</param>
    /// <returns></returns>
    private static InsertHistoryOperation CreateInsertHistoryOperation(IMigrationMetadata migrationMetadatum)
    {
        var model = Convert.FromBase64String(migrationMetadatum.Target);

        var op = new InsertHistoryOperation(
            "__MigrationHistory",
            migrationMetadatum.Id,
            model,
            null);

        return op;
    }

    /// <summary>
    /// Generates the SQL statements for inserting migration into history table.
    /// </summary>
    /// <param name="generator">The generator.</param>
    /// <param name="op">The operation.</param>
    /// <param name="token">The token.</param>
    /// <returns></returns>
    private static IEnumerable<MigrationStatement> GenerateInsertHistoryStatements(
        MigrationSqlGenerator generator,
        InsertHistoryOperation op,
        string token)
    {
        return generator.Generate(new[] { op }, token);
    }

    /// <summary>
    /// Runs the SQL statements on database specified by <paramref name="db"/> (<see cref="DbContext.Database"/>).
    /// </summary>
    /// <param name="statements">The statements.</param>
    /// <param name="db">The db.</param>
    private static void RunSqlStatements(IEnumerable<MigrationStatement> statements, TContext db)
    {
        foreach (var statement in statements)
        {
            db.Database.ExecuteSqlCommand(statement.Sql);
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="CreateDatabaseIfNotExistsAndMigrateToLatest{TContext}"/> class.
    /// </summary>
    /// <param name="migrationsAssembly">The migrations assembly.</param>
    public CreateDatabaseIfNotExistsAndMigrateToLatest(Assembly migrationsAssembly)
    {
        this.migrationsAssembly = migrationsAssembly;
    }

    protected override void Seed(TContext context)
    {
        base.Seed(context);

        // Get migration metadata for migrationAssembly
        var migrationMetadata = GetMigrationMetadata(migrationsAssembly);

        // Crate DbContext
        var db = Activator.CreateInstance<TContext>();
        // Remove newly created record in __MigrationHistory
        db.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");

        // Get provider manifest token
        var token = GetProviderManifestToken(db);
        // Get sql generator
        var generator = GetMigrationSqlGenerator();

        foreach (var migrationMetadatum in migrationMetadata)
        {
            // Create history operation
            var op = CreateInsertHistoryOperation(migrationMetadatum);
            // Generate history insert statements
            var statements = GenerateInsertHistoryStatements(generator, op, token);
            // Run statements (SQL) over database (db)
            RunSqlStatements(statements, db);
        }
    }
}
于 2013-07-24T11:25:14.493 回答