简体   繁体   English

Entity Framework Core - 早期的数据迁移在模型更改时中断

[英]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:考虑以下迁移顺序:

  1. Create Customer , Order , and OrderLine models.创建CustomerOrderOrderLine模型。 Add the schema migrations to load the structures添加架构迁移以加载结构

  2. 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.此时,一切都很好。

  3. 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 生成查询,因此有两种处理数据迁移的选项。

  1. 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.使用DapperADO.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 的任何好处

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM