数据种子设定Data Seeding

数据种子是用初始数据集填充数据库的过程。Data seeding is the process of populating a database with an initial set of data.

可以通过多种方式在 EF Core 中完成此操作:There are several ways this can be accomplished in EF Core:

  • 模型种子数据Model seed data
  • 手动迁移自定义Manual migration customization
  • 自定义初始化逻辑Custom initialization logic

模型种子数据Model seed data

备注

此功能是 EF Core 2.1 中的新增功能。This feature is new in EF Core 2.1.

与在 EF6 中不同,在 EF Core 中,种子设定数据可以作为模型配置的一部分与实体类型相关联。Unlike in EF6, in EF Core, seeding data can be associated with an entity type as part of the model configuration. 然后 EF Core 迁移 可以自动计算在将数据库升级到新版本的模型时需要应用的插入、更新或删除操作。Then EF Core migrations can automatically compute what insert, update or delete operations need to be applied when upgrading the database to a new version of the model.

备注

迁移仅在确定应该执行哪种操作以使种子数据进入所需状态时才考虑模型更改。Migrations only considers model changes when determining what operation should be performed to get the seed data into the desired state. 因此,在迁移外部执行的数据更改可能会丢失或导致错误。Thus any changes to the data performed outside of migrations might be lost or cause an error.

例如,这将为中的配置种子数据 Blog OnModelCreatingAs an example, this will configure seed data for a Blog in OnModelCreating:

modelBuilder.Entity<Blog>().HasData(new Blog {BlogId = 1, Url = "http://sample.com"});

若要添加具有关系的实体,需要指定外键值:To add entities that have a relationship the foreign key values need to be specified:

modelBuilder.Entity<Post>().HasData(
    new Post() { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" });

如果实体类型具有隐藏状态的任何属性,则可以使用匿名类提供值:If the entity type has any properties in shadow state an anonymous class can be used to provide the values:

modelBuilder.Entity<Post>().HasData(
    new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" });

拥有的实体类型可以采用类似的方式进行种子设定:Owned entity types can be seeded in a similar fashion:

modelBuilder.Entity<Post>().OwnsOne(p => p.AuthorName).HasData(
    new { PostId = 1, First = "Andriy", Last = "Svyryd" },
    new { PostId = 2, First = "Diego", Last = "Vega" });

有关更多上下文,请参阅 完整的示例项目See the full sample project for more context.

将数据添加到模型后,应使用 迁移 来应用更改。Once the data has been added to the model, migrations should be used to apply the changes.

提示

如果需要在自动部署中应用迁移,可以创建一个可在执行前预览的 SQL 脚本If you need to apply migrations as part of an automated deployment you can create a SQL script that can be previewed before execution.

或者,您可以使用 context.Database.EnsureCreated() 创建包含种子数据的新数据库,例如,对于测试数据库,或使用内存中提供程序或任何非关系数据库时。Alternatively, you can use context.Database.EnsureCreated() to create a new database containing the seed data, for example for a test database or when using the in-memory provider or any non-relation database. 请注意,如果数据库已存在, EnsureCreated() 将不会更新数据库中的架构或种子数据。Note that if the database already exists, EnsureCreated() will neither update the schema nor seed data in the database. 对于关系数据库, EnsureCreated() 如果计划使用迁移,则不应调用。For relational databases you shouldn't call EnsureCreated() if you plan to use Migrations.

模型种子数据的限制Limitations of model seed data

此类型的种子数据由迁移管理,需要在不连接到数据库的情况下生成用于更新数据库中已存在的数据的脚本。This type of seed data is managed by migrations and the script to update the data that's already in the database needs to be generated without connecting to the database. 这会施加一些限制:This imposes some restrictions:

  • 主键值需要指定,即使它通常由数据库生成。The primary key value needs to be specified even if it's usually generated by the database. 它用于检测迁移间的数据更改。It will be used to detect data changes between migrations.
  • 如果以任何方式更改了主键,则将删除以前的种子数据。Previously seeded data will be removed if the primary key is changed in any way.

因此,此功能最适用于不需要在迁移外更改的静态数据,并且不依赖于数据库中的任何其他内容(例如,邮政编码)。Therefore this feature is most useful for static data that's not expected to change outside of migrations and does not depend on anything else in the database, for example ZIP codes.

如果你的方案包括以下任何一种情况,则建议使用上一部分中所述的自定义初始化逻辑:If your scenario includes any of the following it is recommended to use custom initialization logic described in the last section:

  • 用于测试的临时数据Temporary data for testing
  • 依赖于数据库状态的数据Data that depends on database state
  • 需要由数据库生成的键值的数据,包括使用替代密钥作为标识的实体Data that needs key values to be generated by the database, including entities that use alternate keys as the identity
  • 需要自定义转换 (的数据,该转换不) 值转换 处理,如某些密码哈希Data that requires custom transformation (that is not handled by value conversions), such as some password hashing
  • 需要调用外部 API 的数据,例如 ASP.NET Core 标识角色和用户创建Data that requires calls to external API, such as ASP.NET Core Identity roles and users creation

手动迁移自定义Manual migration customization

添加迁移时,对指定的数据所做的更改将 HasData 转换为对 InsertData() 、和的调用 UpdateData() DeleteData()When a migration is added the changes to the data specified with HasData are transformed to calls to InsertData(), UpdateData(), and DeleteData(). 解决某些限制的一种方法 HasData 是手动将这些调用或 自定义操作 添加到迁移。One way of working around some of the limitations of HasData is to manually add these calls or custom operations to the migration instead.

migrationBuilder.InsertData(
    table: "Blogs",
    columns: new[] { "Url" },
    values: new object[] { "http://generated.com" });

自定义初始化逻辑Custom initialization logic

执行数据种子设定的一种简单而有效的方法是在 DbContext.SaveChanges() 主应用程序逻辑开始执行之前使用。A straightforward and powerful way to perform data seeding is to use DbContext.SaveChanges() before the main application logic begins execution.

using (var context = new DataSeedingContext())
{
    context.Database.EnsureCreated();

    var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
    if (testBlog == null)
    {
        context.Blogs.Add(new Blog { Url = "http://test.com" });
    }
    context.SaveChanges();
}

警告

种子设定代码不应是正常应用执行的一部分,因为这可能会导致多个实例运行时出现并发性问题,并且还要求应用有权修改数据库架构。The seeding code should not be part of the normal app execution as this can cause concurrency issues when multiple instances are running and would also require the app having permission to modify the database schema.

根据部署的约束,可以通过不同的方式执行初始化代码:Depending on the constraints of your deployment the initialization code can be executed in different ways:

  • 在本地运行初始化应用程序Running the initialization app locally
  • 将初始化应用与主应用一起部署,调用初始化例程,禁用或删除初始化应用。Deploying the initialization app with the main app, invoking the initialization routine and disabling or removing the initialization app.

通常可以使用 发布配置文件自动完成此配置。This can usually be automated by using publish profiles.