使桌面客户端无需更新即可使用较新的 EF 数据库迁移
Make desktop client work with newer EF db migration without updating
目前我有一个 ERP,它是一个基于 Winforms 的客户端(带有 SQL 服务器),它使用 ClickOnce 在桌面上交付和更新。
当前版本首先使用 Entity Framework 4(基于 ObjectContext)和数据库。当数据库模式发生变化时,我对客户端进行更新的方式是一个四步过程:
- 在生产中使用兼容的列创建一个中间更新的数据库模式(在任何地方都允许 null 或具有默认值等)。旧客户端可以连接到该数据库并继续工作,就好像什么都没有改变一样
- 将桌面客户端更新到具有更新功能的中间版本,该更新功能说明了该中间架构但具有所有 "final schema" 功能
- 更新所有客户端并且所有记录都与 "final" 架构兼容后,使用所需的数据库约束对架构进行新更新
- 将所有客户端更新到映射到此最终模式的最终版本(它解决了数据库约束错误,并且需要这些模式更改才能工作)。
我发现这个过程虽然对我们来说有点麻烦,但对客户来说更好,他们可以在他们认为合适的时候更新,并且不会在工作中途被更新打扰(这可能涉及到有不想等待软件更新的客户。
现在我几乎完全重写了客户端(仍然是 Winforms),使用 EF6 和代码优先,并进行了迁移。
我一直在搜索文档,但找不到任何东西(现在似乎只有网络编程,通常可以同时更新数据库和网络客户端,而不会打扰用户),但是一旦我在生产中应用迁移,未更新的客户端不能再使用数据库。如果实例化上下文与数据库模式不同步,EF 将在实例化上下文时抱怨并抛出异常。
具体问题:有没有一种方法可以让 EF6 代码优先 dbcontext 与更新的数据库模式迁移一起工作,而不是编译的,只要它兼容?如果是这样的话,我可以继续做我目前正在做的事情。
如果有人想扩展实际答案,还有一个(我猜)基于意见的问题:有没有更好的方法来处理这种情况?我敢肯定我不是唯一遇到此问题的人,但是 Google 文档所需的关键字太宽泛,到目前为止,我的搜索中只出现了网络场景。
我目前正处于允许进行重大更改的客户端重写阶段,所以我不关心解决方案是否会使部分代码复杂化
当应用程序通过直接调用 DbContext.Database.Initialize
或实例化第一个 DbContext
来初始化模型数据库时,它会检查应用程序中的模型和数据库中的模型是否匹配。
为此,它计算模型哈希,并将其与存储在 __MigrationHistory
table(或 EdmMetadata
table 中的哈希进行比较,如果它是从 EF 4.x) 更新的。这是在 System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel
方法中完成的,该方法接收一个名为 throwIfNoMetadata
的参数,在内部实现中恰好是 false
,因此如果没有元数据则不会抛出异常。[=26] =]
因此,如果您在数据库初始化之前以某种方式让这个 tables 消失,就可以避免错误。重要的一点是您必须在不使用 DbContext
的情况下进行此更改。如果不存在,数据库将尝试初始化,如果 table 存在,它将失败。所以你可以使用普通 ADO.NET
来删除 tables.
考虑到元数据 table 可以自动创建,例如通过应用迁移。
您还可以使用ctx.Database.CompatibleWithModel(true)
检查数据库元数据是否存在以及是否兼容,以摆脱它。参数就是我上面说的throwIfNoMetadata
数据库初始值设定项中的兼容性检查:
默认的 DB Initializer 是 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();
}
}
目前我有一个 ERP,它是一个基于 Winforms 的客户端(带有 SQL 服务器),它使用 ClickOnce 在桌面上交付和更新。
当前版本首先使用 Entity Framework 4(基于 ObjectContext)和数据库。当数据库模式发生变化时,我对客户端进行更新的方式是一个四步过程:
- 在生产中使用兼容的列创建一个中间更新的数据库模式(在任何地方都允许 null 或具有默认值等)。旧客户端可以连接到该数据库并继续工作,就好像什么都没有改变一样
- 将桌面客户端更新到具有更新功能的中间版本,该更新功能说明了该中间架构但具有所有 "final schema" 功能
- 更新所有客户端并且所有记录都与 "final" 架构兼容后,使用所需的数据库约束对架构进行新更新
- 将所有客户端更新到映射到此最终模式的最终版本(它解决了数据库约束错误,并且需要这些模式更改才能工作)。
我发现这个过程虽然对我们来说有点麻烦,但对客户来说更好,他们可以在他们认为合适的时候更新,并且不会在工作中途被更新打扰(这可能涉及到有不想等待软件更新的客户。
现在我几乎完全重写了客户端(仍然是 Winforms),使用 EF6 和代码优先,并进行了迁移。
我一直在搜索文档,但找不到任何东西(现在似乎只有网络编程,通常可以同时更新数据库和网络客户端,而不会打扰用户),但是一旦我在生产中应用迁移,未更新的客户端不能再使用数据库。如果实例化上下文与数据库模式不同步,EF 将在实例化上下文时抱怨并抛出异常。
具体问题:有没有一种方法可以让 EF6 代码优先 dbcontext 与更新的数据库模式迁移一起工作,而不是编译的,只要它兼容?如果是这样的话,我可以继续做我目前正在做的事情。
如果有人想扩展实际答案,还有一个(我猜)基于意见的问题:有没有更好的方法来处理这种情况?我敢肯定我不是唯一遇到此问题的人,但是 Google 文档所需的关键字太宽泛,到目前为止,我的搜索中只出现了网络场景。
我目前正处于允许进行重大更改的客户端重写阶段,所以我不关心解决方案是否会使部分代码复杂化
当应用程序通过直接调用 DbContext.Database.Initialize
或实例化第一个 DbContext
来初始化模型数据库时,它会检查应用程序中的模型和数据库中的模型是否匹配。
为此,它计算模型哈希,并将其与存储在 __MigrationHistory
table(或 EdmMetadata
table 中的哈希进行比较,如果它是从 EF 4.x) 更新的。这是在 System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel
方法中完成的,该方法接收一个名为 throwIfNoMetadata
的参数,在内部实现中恰好是 false
,因此如果没有元数据则不会抛出异常。[=26] =]
因此,如果您在数据库初始化之前以某种方式让这个 tables 消失,就可以避免错误。重要的一点是您必须在不使用 DbContext
的情况下进行此更改。如果不存在,数据库将尝试初始化,如果 table 存在,它将失败。所以你可以使用普通 ADO.NET
来删除 tables.
考虑到元数据 table 可以自动创建,例如通过应用迁移。
您还可以使用ctx.Database.CompatibleWithModel(true)
检查数据库元数据是否存在以及是否兼容,以摆脱它。参数就是我上面说的throwIfNoMetadata
数据库初始值设定项中的兼容性检查:
默认的 DB Initializer 是 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();
}
}