[英]Entity Framework Core - Earlier data migrations break when models are changed
We are starting a new application with with .NET Core, EF Core and Postgres - we're really liking the migrations:-)我们正在使用 .NET Core、EF Core 和 Postgres 启动一个新应用程序——我们真的很喜欢这些迁移:-)
However, I've found a problem that I can't figure out how to work around.但是,我发现了一个我不知道如何解决的问题。 In some of our migrations, we use the
DbContext
to load records, run some business logic, and resave the records.在我们的一些迁移中,我们使用
DbContext
来加载记录、运行一些业务逻辑并重新保存记录。 We also run database.Context.Migrate()
when the web server starts, so it always runs.我们还在 web 服务器启动时运行
database.Context.Migrate()
,因此它始终运行。
This all works fine (so far).这一切都很好(到目前为止)。
Unfortunately it works right up until the model is changed at a later point.不幸的是,直到稍后更改 model 之前,它一直有效。 Consider the following migration order:
考虑以下迁移顺序:
Create Customer
, Order
, and OrderLine
models.创建
Customer
、 Order
和OrderLine
模型。 Add the schema migrations to load the structures添加架构迁移以加载结构
A business rule changes, so we need to set a value on the Order
record based on some business logic.业务规则发生变化,因此我们需要根据一些业务逻辑在
Order
记录上设置一个值。 We create a data migration where we load the DbContext
, run the code, and run SaveChangesAsync()
我们创建一个数据迁移,在其中加载
DbContext
、运行代码并运行SaveChangesAsync()
At this point, everything is fine.此时,一切都很好。
We need to add a schema migration with a new field to the Customer
record.我们需要向
Customer
记录添加一个带有新字段的模式迁移。
Here, things break.在这里,事情破裂了。 If the database has not had migration 2 applied, the
Migrate()
command errors as when applying migration 2, EF is trying to generate a select statement that includes the new field in 3.如果数据库尚未应用迁移 2,
Migrate()
命令会出错,因为在应用迁移 2 时,EF 正在尝试生成包含 3 中新字段的 select 语句。
I'm not sure where the problem actually is in the process though: should we not be using the DbContext
in the migration (and hand-code SQL statements)?不过,我不确定问题实际上出在哪里:我们不应该在迁移中使用
DbContext
(以及手动编码 SQL 语句)吗? Should we be explicitly be doing projections on every record read so that we know exactly what we're doing?我们是否应该明确地对每条记录进行预测,以便我们确切地知道我们在做什么?
Something else?还有别的吗?
In your example, 1 and 3 are Schema Migrations and 2 is a Data Migration.在您的示例中,1 和 3 是架构迁移,2 是数据迁移。
Since Entity Framework Core will generate queries based off the current models in code, and not the current state of the database, there is two options for handling Data Migrations.由于 Entity Framework Core 将根据代码中的当前模型而不是数据库的当前 state 生成查询,因此有两种处理数据迁移的选项。
Perform Data Migrations In-Order with Schema Migrations使用架构迁移按顺序执行数据迁移
Use something like Dapper or ADO.NET to perform data migrations based on the schema at the time the migration is written.使用Dapper或ADO.NET 之类的东西根据迁移编写时的模式执行数据迁移。 You can get the
DbConnection
object from the EF Context using context.Database.GetDbConnection()
您可以使用
context.Database.GetDbConnection()
从 EF Context 获取DbConnection
object
Pros: Migration code will never need to be changed优点:永远不需要更改迁移代码
Cons: You won't get any of the benefits of EF Core缺点:您不会获得 EF Core 的任何好处
Perform Data Migrations After Schema Migrations在架构迁移后执行数据迁移
EF performs migrations in the ascending order of the MigrationAttribute.Id
string value. EF 按
MigrationAttribute.Id
字符串值的升序执行迁移。 EF generates this based off a timestamp when calling do.net ef migrations add xxx
. EF 在调用
do.net ef migrations add xxx
时基于时间戳生成它。 You can change this attribute to ensure Data Migrations are run after all Schema Migrations.您可以更改此属性以确保数据迁移在所有模式迁移之后运行。 Change the filename as well for readability so that they stay in the order they will be applied.
更改文件名以及可读性,以便它们保持它们将被应用的顺序。
Pros: You get all the benefits of EF Core优点:您可以获得 EF Core 的所有好处
Cons: If the schema changes in the future, such as removing a column referenced in the data migration, you will need to modify your migration缺点:如果以后schema发生变化,比如删除数据迁移中引用的列,则需要修改你的迁移
Example MigrationAttribute.Id
change:示例
MigrationAttribute.Id
更改:
// change
[Migration("20191002171530_DataMigration")]
// to
[Migration("99999999000001_DataMigration")]
I faced a similar issue and landed on this question;我遇到了类似的问题并解决了这个问题; I thought I'd share the technique I used to resolve my problem for posterity.
我想我会分享我用来为后代解决问题的技术。
In my case, we had a model Something
that initially had a field/database column property
, but the field was deprecated because property
ended up being stored elsewhere.在我的例子中,我们有一个 model
Something
最初有一个字段/数据库列property
,但该字段已被弃用,因为property
最终存储在其他地方。 We wrote a data migration that referenced Something.property
and moved it to its new location.我们编写了引用
Something.property
的数据迁移并将其移动到新位置。
We wanted to update the domain model to remove property
from Something
as well.我们想更新域 model 以从
Something
中删除property
。 However, doing so would break the typechecking in the data migration.但是,这样做会破坏数据迁移中的类型检查。
We must have a type that has property
on it to allow the data migration to typecheck.我们必须有一个具有
property
的类型,以允许数据迁移进行类型检查。 However, we must not have property
on the domain model.但是,我们不能在域 model 上拥有
property
。
The solution was to introduce MigrationDbContext
, which subclasses the standard DbContext
.解决方案是引入
MigrationDbContext
,它是标准DbContext
的子类。 MigrationDbContext
exposes a second projection of the Somethings
table, called Somethings_WithProperty
. MigrationDbContext
公开了Somethings
表的第二个投影,称为Somethings_WithProperty
。 This projection is a public DbSet<SomethingWithProperty>
.此投影是
public DbSet<SomethingWithProperty>
。 The SomethingWithProperty
class is a record of the historical type of the model with its contemporary properties; SomethingWithProperty
class 是 model 的历史类型及其当代属性的记录; and importantly, we only allow the Type to show up in our Migrations
folder.重要的是,我们只允许 Type 出现在我们的
Migrations
文件夹中。 SomethingWithProperty
is stored in Migrations/HistoricalModels
. SomethingWithProperty
存储在Migrations/HistoricalModels
中。
Now, my data migration acquires a MigrationDbContext
from dependency injection instead of a DbContext
.现在,我的数据迁移从依赖注入而不是
DbContext
获取了MigrationDbContext
。 I can replace the reference to dbcontext.Somethings
with dbcontext.Somethings_WithProperty
, and the data migration can typecheck.我可以用
dbcontext.Somethings_WithProperty
替换对dbcontext.Somethings
的引用,并且数据迁移可以进行类型检查。 My domain model on the other hand will never be procured from the MigrationDbContext
, so the only Something
s it can get are from the Somethings
DbSet, which correctly omits the property
that we dropped.另一方面,我的域 model 永远不会从
MigrationDbContext
中获取,因此它唯一可以获得的Something
是来自Somethings
DbSet,它正确地省略了我们删除的property
。
A few caveats;一些注意事项; if your DbContext constructor takes a generic
DbContextOptions<MyDbContext>
parameter, then to satisfy dependency injection, you will need to introduce a protected
constructor on MyDbContext
that takes a DbContextOptions<MigrationDbContext>
parameter;如果您的 DbContext 构造函数采用通用
DbContextOptions<MyDbContext>
参数,那么为了满足依赖注入,您需要在采用DbContextOptions<MigrationDbContext>
参数的MyDbContext
上引入protected
的构造函数; the child class can invoke this in its constructor.孩子 class 可以在其构造函数中调用它。 Also,
MigrationDbContext
will initially be confused that two projections are mapped to the same table;此外,
MigrationDbContext
最初会混淆两个投影映射到同一个表; in its SetupSomethingsWithProperty
method you should include a foreign key referencing the natural projection to clear up the confusion.在其
SetupSomethingsWithProperty
方法中,您应该包含一个引用自然投影的外键以消除混淆。
private static void SetupSomethingsWithProperty(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Something_WithProperty>(s =>
{
s.ToTable("Somethings");
s.HasOne<Something>().WithOne().HasForeignKey<Something_WithProperty>(r => r.ID);
});
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.