使桌面客户端无需更新即可使用较新的EF db迁移

时间:2016-03-11 07:41:55

标签: c# entity-framework desktop-application ef-migrations

目前我有一个基于Winforms的客户端(使用SQL Server),可以使用ClickOnce在桌面上交付和更新。

当前版本首先使用Entity Framework 4(基于ObjectContext)和数据库。当数据库架构发生变化时,我对客户端进行更新的方式是一个四步过程:

  1. 使用兼容列在生产中创建中间更新的数据库模式(在任何地方允许null或具有默认值等)。旧客户端可以连接到该数据库并继续工作,就像没有任何更改一样
  2. 将桌面客户端更新为具有更新功能的中间版本,这些功能会考虑此中间架构但具有所有"最终架构"特征
  3. 所有客户端更新后,所有记录都与" final"架构,使用所需的数据库约束对架构进行新的更新
  4. 将所有客户端更新为最终版本,该版本映射到此最终架构(考虑到数据库约束错误,并且需要这些架构更改才能工作)。
  5. 我发现这个过程对我们来说有点麻烦,对于客户来说更好,他们可以在他们认为合适的时候进行更新,并且不会因他们中间的更新而中断工作(可能涉及让客户在他们面前不想等待软件更新)。

    现在我已经使用EF6和代码优先进行了几乎完全重写的客户端(仍然是Winforms)和迁移。

    我一直在寻找文档但却找不到任何东西(这些天似乎只有网络编程,通常可以同时对数据库和Web客户端进行更新而不会中断用户),但是一旦我在生产中应用迁移,非更新的客户端就无法再使用数据库了。如果上下文与数据库模式不一致,EF会在实例化上下文时抱怨并抛出异常。

    具体问题:有没有办法让EF6代码优先dbcontext与数据库模式的新迁移一起使用,只要它是兼容的?如果是这样的话,我可以继续做我到目前为止所做的事情。

    一个(我猜)基于意见的问题,如果有人想扩展实际答案:有没有更好的方法来处理这种情况?我确定我不是唯一一个遇到这个问题的人,但Google提供的文档关键词过于广泛,到目前为止,我的搜索只出现了网络方案。

    我目前处于客户端重写的阶段,可以允许进行重大更改,因此我不在乎解决方案是否会使代码的某些部分复杂化

1 个答案:

答案 0 :(得分:3)

当应用程序初始化模型数据库时,通过直接调用DbContext.Database.Initialize或实例化第一个DbContext来执行,它会检查应用程序中的模型和数据库中的模型是否匹配。

为此,它计算模型哈希,并将其与存储在__MigrationHistory表中的哈希(或EdmMetadata表中的哈希值进行比较,如果它是从EF 4.x更新的)。这是在System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel方法中完成的,该方法接收名为throwIfNoMetadata的参数,该参数在内部实现中恰好是false,因此如果没有元数据则不会抛出异常。

因此,如果在初始化数据库之前以某种方式使这些表消失,则可以避免错误。重要的是,您必须在不使用DbContext的情况下进行此更改。如果没有,数据库将尝试初始化,如果此表存在,它将失败。因此,您可以使用普通ADO.NET删除表格。

考虑到可以自动创建元数据表,例如通过应用迁移。

您还可以使用ctx.Database.CompatibleWithModel(true)检查数据库元数据是否存在且是否兼容,以便将其删除。该参数恰好是我在上面提到的throwIfNoMetadata

db initializers中的兼容性检查:

默认数据库初始化程序为CreateDatabaseIfNotExists,它会检查模型兼容性,throwIfNoMetadata设置为false。这就是这个解决方案有效的原因。但是,如果您实现了自己的DB Initializer版本而没有运行检查,那么它就会工作。

public virtual void InitializeDatabase(TContext context)
{
    Check.NotNull(context, "context");

    var existence = new DatabaseTableChecker().AnyModelTableExists(context.InternalContext);

    if (existence == DatabaseExistenceState.Exists)
    {
        // If there is no metadata either in the model or in the database, then
        // we assume that the database matches the model because the common cases for
        // these scenarios are database/model first and/or an existing database.
        if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false, existenceState: existence))
        {
            throw Error.DatabaseInitializationStrategy_ModelMismatch(context.GetType().Name);
        }
    }
    else
    {
        // Either the database doesn't exist, or exists and is considered empty
        context.Database.Create(existence);
        Seed(context);
        context.SaveChanges();
    }
}