简体   繁体   English

尝试EF代码优先和迁移 - 绊脚石

[英]Trying out EF code-first and migrations - stumbling blocks

I've always been a database oriented programmer, so up to this day, I've always used a database-driven approach to programming and I feel pretty confident in T-SQL and SQL Server. 我一直是一个面向数据库的程序员,所以直到今天,我总是使用数据库驱动的编程方法,我对T-SQL和SQL Server非常有信心。

I'm trying to wrap my head around the Entity Framework 6 code-first approach - and frankly - I'm struggling. 我试图围绕Entity Framework 6 代码优先的方法 - 坦率地说 - 我正在努力。

I have an existing database - so I did a Add New Item > ADO.NET Entity Data Model > Code-First from Database and I get a bunch of C# classes representing my existing database. 我有一个现有的数据库 - 所以我Add New Item > ADO.NET Entity Data Model > Code-First from Database做了一个Add New Item > ADO.NET Entity Data Model > Code-First from Database ,我得到了一堆代表我现有数据库的C#类。 So far so good. 到现在为止还挺好。

What I'm trying to do now is explore how to handle ongoing database upgrades - both in schema as well as "static" (pre-populated) lookup data. 我现在要做的是探索如何处理正在进行的数据库升级 - 包括模式和“静态”(预先填充的)查找数据。 My first gripe is that the entities that were reverse-engineered from the database are being configured with the Fluent API, while it seems more natural to me to create the new tables I want to have created as a C# class with data annotations. 我的第一个抱怨是,从数据库进行逆向工程的实体正在使用Fluent API进行配置,而我似乎更自然地创建了我想要创建的具有数据注释的C#类的新表。 Is there any problems / issues with "mixing" those two approaches? “混合”这两种方法是否有任何问题/问题? Or could I tell the reverse-engineering step to just use data annotation attributes instead of the Fluent API altogether? 或者,我可以告诉逆向工程步骤只使用数据注释属性而不是Fluent API吗?

My second and even bigger gripe: I'm trying to create nice and small migrations - one each for each set of features I'm trying to add (eg a new table, a new index, a few new columns etc.) - but it seems I cannot have more than a single "pending" migration...... when I have one, and I modify my model classes further, and I try to get a second migration using add-migration (name of migration) , I'm greeted with: 我的第二个甚至更大的抱怨:我正在尝试创建漂亮的小型迁移 - 每个我想要添加的功能集(例如新表,新索引,一些新列等) - 但是似乎我不能只有一个“挂起”的迁移......当我有一个,我进一步修改我的模型类,并尝试使用add-migration (name of migration)进行第二次迁移,我受到了欢迎:

Unable to generate an explicit migration because the following explicit migrations are pending: [201510061539107_CreateTableMdsForecast]. 无法生成显式迁移,因为以下显式迁移尚未处理:[201510061539107_CreateTableMdsForecast]。 Apply the pending explicit migrations before attempting to generate a new explicit migration. 在尝试生成新的显式迁移之前应用挂起的显式迁移。

Seriously ?!?!? 说真的?!?!? I cannot have more than one, single pending migration?? 我不能有多个,单个挂起的迁移? I need to run update-database after every single tiny migration I'm adding? 我需要在每次添加的微小迁移后运行update-database

Seems like a rather BIG drawback! 似乎是一个相当大的缺点! I'd much rather create my 10, 20 small, compact, easy-to-understand migrations, and then apply them all in one swoop - no way to do this!?!? 我宁愿创建我的10个,20个小型,紧凑,易于理解的迁移, 然后一举应用它们 - 无法做到这一点!?!? This is really hard to believe..... any way around this?? 这真的很难相信.....任何方式?

It is true that you can only have one pending migration open at a time during development . 确实,您在开发期间一次只能打开一个挂起的迁移。 To understand why, you have to understand how the migrations are generated. 要了解原因,您必须了解如何生成迁移。 The generator works by comparing the current state of your database (the schema) with the current state of your model code. 生成器通过将数据库的当前状态(模式)与模型代码的当前状态进行比较来工作。 It then effectively creates a "script" (a C# class) which changes the schema of the database to match the model. 然后它有效地创建了一个“脚本”(C#类),它改变了数据库的模式以匹配模型。 You would not want to have more than one of these pending at the same time or else the scripts would conflict with each other. 您不希望同时有多个挂起,否则脚本会相互冲突。 Let's take an simple example: 我们举一个简单的例子:

Let's say I have a class Widget : 假设我有一个类Widget

class Widget
{
    public int Id { get; set; }
    public string Name { get; set; }
}

and a matching table Widgets in the database: 和数据库中的匹配表Widgets

Widgets
-------
Id (int, PK, not null)
Name (nvarchar(100), not null)

Now I decide to add a new property Size to my class. 现在我决定在我的班级中添加一个新的属性Size

class Widget
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Size { get; set; }  // added
}

When I create my migration, the generator looks at my model, compares it with the database and sees that my Widget model now has a Size property while the corresponding table does not have a Size column. 当我创建我的迁移时,生成器查看我的模型,将其与数据库进行比较,并看到我的Widget模型现在具有Size属性,而相应的表没有Size列。 So the resulting migration ends up looking like this: 因此,最终的迁移最终看起来像这样:

public partial class AddSizeToWidget : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Widgets", "Size", c => c.Int());
    }

    public override void Down()
    {
        DropColumn("dbo.Widgets", "Size");
    }
}

Now, imagine that it is allowed to create a second migration while the first is still pending. 现在,假设允许创建第二个迁移,而第一个迁移仍处于暂挂状态。 I haven't yet run the Update-Database command, so my baseline database schema is still the same. 我还没有运行Update-Database命令,所以我的基线数据库模式仍然相同。 Now I decide to add another property Color to Widget . 现在我决定添加另一个属性Color to Widget

When I create a migration for this change, the generator compares my model to the current state of the database and sees that I have added two columns. 当我为此更改创建迁移时,生成器会将我的模型与数据库的当前状态进行比较,并看到我添加了列。 So it creates the corresponding script: 所以它创建了相应的脚本:

public partial class AddColorToWidget : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Widgets", "Size", c => c.Int());
        AddColumn("dbo.Widgets", "Color", c => c.Int());
    }
    ...
}

So now I have two pending migrations, and both of them are going to try to add a Size column to the database when they are ultimately run. 所以现在我有两个待定的迁移,并且它们都将在最终运行时尝试将Size列添加到数据库。 Clearly, that is not going to work. 显然,这是行不通的。 So that is why there is only one pending migration allowed to be open at a time. 这就是为什么一次只允许打开一个挂起的迁移的原因。

So, the general workflow during development is: 因此,开发过程中的一般工作流程是:

  1. Change your model 改变你的模型
  2. Generate a migration 生成迁移
  3. Update the database to establish a new baseline 更新数据库以建立新基准
  4. Repeat 重复

If you make a mistake, you can roll back the database to a previous migration using the –TargetMigration parameter of the Update-Database command, then delete the errant migration(s) from your project and generate a new one. 如果出错,可以使用Update-Database命令的–TargetMigration参数将数据库回滚到先前的迁移,然后从项目中删除错误的迁移并生成新的迁移。 (You can use this as a way to combine several small migrations into a larger chunk if you really want to, although I find in practice it is not worth the effort). (你可以使用它作为一种方法,如果你真的想要将几个小的迁移组合成一个更大的块,虽然我发现在实践中它不值得努力)。

Update-Database –TargetMigration PreviousMigrationName

Now, when it comes time to update a production database, you do not have to manually apply each migration one at a time. 现在,当需要更新生产数据库时,您不必一次手动应用每个迁移。 That is the beauty of migrations -- they are applied automatically whenever you run your updated code against the database. 这就是迁移之美 - 只要您针对数据库运行更新的代码,它们就会自动应用。 During initialization, EF looks at the target database and checks the migration level (this is stored in the special __MigrationHistory table which was created when you enabled migrations on the database). 在初始化期间,EF查看目标数据库并检查迁移级别(这存储在您在数据库上启用迁移时创建的特殊__MigrationHistory表中)。 For any migration in your code which has not yet been applied, it runs them all in order for you, to bring the database up to date. 对于尚未应用的代码中的任何迁移,它将为您运行所有迁移,以使数据库保持最新状态。

Hope this helps clear things up. 希望这有助于澄清事情。

Is there any problems / issues with "mixing" those two approaches? “混合”这两种方法是否有任何问题/问题?

  1. No, there is no problem to mix them. 不,混合它们没有问题。
  2. You can do more with fluent config than with data annotations. 您可以使用流畅的配置而不是数据注释来完成更多工作。
  3. Fluent config overrides data annotation when constructing the migration script. 在构造迁移脚本时,Fluent配置会覆盖数据注释。
  4. You can use data annotations to generate DTOs and front-end/UI constraints dynamically - saves a lot of code. 您可以使用数据注释动态生成DTO和前端/ UI约束 - 节省大量代码。
  5. Fluent API has class EntityTypeConfiguration which allows you to make domains (in DDD sense) of objects dynamically and store them - speeds up work with DbContext a lot. Fluent API具有类EntityTypeConfiguration,它允许您动态创建对象的域(在DDD意义上)并存储它们 - 加快DbContext的工作速度。

I cannot have more than a single "pending" migration 我不能只有一个“待定”的迁移

  1. Not 100% true. 不是100%真实。 ( Maybe 50% but this is not a showstopper ) (也许50%,但这不是一个表明)
  2. Yes, the DbMigrator compares your model "hash" to the database model "hash" when it generates the Db - so it blocks you before you make your new small migration. 是的,DbMigrator在生成Db时将模型“hash”与数据库模型“hash”进行比较 - 因此它会在您进行新的小型迁移之前阻止您。 But this is not a reason to think you can not make small migration steps. 但这并不是认为你不能做出小规模迁移步骤的理由。 I do only small migration steps all the time. 我一直只做很小的迁移步骤。
  3. When you develop an app and you use your local db you apply the small migrations one by one as you develop functionality - gradually. 当您开发应用程序并使用本地数据库时,在逐步开发功能时逐个应用小型迁移。 At the end you deploy to staging/production all your small migrations in one dll with all the new functionality - and they are applied one by one. 最后,您将在一个dll中使用所有新功能部署到暂存/生产所有小型迁移 - 并逐个应用它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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