EntityFrameworkCore教程:Data-Seeding(种子数据)
一、什么是Data-Seeding
Data-Seeding是EntityFrameworkCore 2.1以上版本新增加的特性。在项目刚开始的时候,我们往往是需要初始化一些基础数据到数据库中,通过Data-Seeding特性就可以实现这一功能。本篇文章我们将讲解如何进行数据初始化。
二、初始化方法
具体的数据初始化方法分为如下三种:
- 模型中配置。这种是通过调用HasData()方法。
- 手动迁移时添加。
- 自定义初始化逻辑。
下面我们分别来讲解如何使用这三种方式进行数据迁移。
1、模型中配置
这种方式是通过调用HasData()方法实现的。这种也是我在项目开发过程中,经常使用的。这种方式是在数据上下文类中重写OnModelCreating()方法,我们先看HasData()方法的定义:
可以看到,方法的参数可以是Blog类型的数组,具体代码如下:
using EFCore.Model; using Microsoft.EntityFrameworkCore; namespace EFCore.Data { /// <summary> /// 数据上下文 /// </summary> public class EFDbContext:DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=EFTest;User ID=sa;Password=123456;"); } public DbSet<Blog> Blogs { get; set; } /// <summary> /// 重写OnModelCreating方法 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(ModelBuilder modelBuilder) { // 针对Blog实体添加种子数据 modelBuilder.Entity<Blog>().HasData( new Blog() { // Id字段要赋值,否则会报错 Id=1, Name="ef core" }, new Blog() { Id=2, Name="ASP.NET Core" }, new Blog() { Id=3, Name="图解数据结构" } ); base.OnModelCreating(modelBuilder); } } }
我们注意到:默认情况下会自动设置Id列为主键,并且是自动增长的。但是这里要设置Id的值,即使Id是自动生成的主键,否则会报下图所示的错误:
添加完种子数据以后,我们运行程序,查看输出结果:
查看数据库:
这样就生成了数据库和表,而且表里面也有了初始化数据。
假如这时候我们想增加一条数据,代码如下:
using EFCore.Model; using Microsoft.EntityFrameworkCore; namespace EFCore.Data { /// <summary> /// 数据上下文 /// </summary> public class EFDbContext:DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=EFTest;User ID=sa;Password=123456;"); } public DbSet<Blog> Blogs { get; set; } /// <summary> /// 重写OnModelCreating方法 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(ModelBuilder modelBuilder) { // 针对Blog实体添加种子数据 modelBuilder.Entity<Blog>().HasData( new Blog() { // Id字段要赋值,否则会报错 Id=1, Name="ef core" }, new Blog() { Id=2, Name="ASP.NET Core" }, new Blog() { Id=3, Name="图解数据结构" }, // 新增加一条数据 new Blog() { Id=4, Name="C#高级编程" } ); base.OnModelCreating(modelBuilder); } } }
这时候还能不能用刚才的方法呢?我们这时在运行程序,查看结果:
这时候程序执行失败了,而且表里面的数据也没有增加。这说明context.Database.EnsureCreated()方法只有在第一次执行的时候才会有效,以后数据进行更改后就无效了。那么有什么方式可以实现呢?这时只有通过命令行进行迁移或者通过context.Database.Migrate方法调用生成的迁移类才能对数据的更改有效。
我们把刚才生成的数据库删掉,新增加的那条数据注释掉,然后使用命令行迁移的方式生成数据库表,首先添加迁移:
然后更新数据库:
更新完数据库以后,我们在HasData()方法里面添加一条数据,然后再次执行上面的添加迁移和更新数据库的命令,发现这时候数据库里面会添加新增加的数据。
在执行完第二次添加迁移命令后,如果不使用更新数据库命令,也可以通过代码的方式进行迁移,这就是调用context.Database.Migrate命令,代码如下:
using EFCore.Data; using Microsoft.EntityFrameworkCore; using System; namespace EFCore.Con { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); EFDbContext dbContext = new EFDbContext(); // 迁移 dbContext.Database.Migrate(); //bool tfTrue = dbContext.Database.EnsureCreated(); //if(tfTrue) //{ // Console.WriteLine("数据库创建成功!"); //} //else //{ // Console.WriteLine("数据库创建失败!"); //} Console.ReadKey(); } } }
这时会自动调用最新的迁移文件去更新数据库。
注意:调用该方法对数据的更改只有在迁移时才能生效,也就是说只有通过命令进行迁移或者通过context.Database.Migrate方法调用生成的迁移类才能对数据更改有效。而调用context.Database.EnsureCreated()方法只有在第一次执行的时候才有效,但数据进行更改后将无效。
这种方式有两种限制:
- 必须指定主键的值(即使主键由数据库自动生成也要指定值)。
- 添加的必须是静态数据,没有任何的依赖。比如添加的Id主键的值在其它表里面有引用就不可以。
2、手动迁移时添加
这种方式在这里不进行讲解,有兴趣的可以参考微软的官方文档。
3、自定义初始化逻辑
执行数据种子设定的一种简单而有效的方法是在主应用程序逻辑开始执行之前使用使用DbContext.SaveChanges()。代码如下:
using EFCore.Data; using EFCore.Model; using Microsoft.EntityFrameworkCore; using System; using System.Linq; namespace EFCore.Con { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); //EFDbContext dbContext = new EFDbContext(); //// 迁移 //dbContext.Database.Migrate(); ////bool tfTrue = dbContext.Database.EnsureCreated(); ////if(tfTrue) ////{ //// Console.WriteLine("数据库创建成功!"); ////} ////else ////{ //// Console.WriteLine("数据库创建失败!"); ////} #region 使用自定义初始化逻辑 using(EFDbContext context=new EFDbContext()) { context.Database.EnsureCreated(); var testBlog = context.Blogs.FirstOrDefault(p => p.Name == "C#"); if(testBlog == null) { // 添加数据 context.Blogs.Add(new Blog() { Name = "C#" }); } // 保存数据 context.SaveChanges(); } #endregion Console.ReadKey(); } } }
通过这种自定义逻辑的方式也可以添加种子数据。如果HasData()方法里面添加了种子数据,那么会先把HasData()方法里面的种子数据添加到数据库中。如果没有名称为C#的数据,则还会在添加一条数据。
如果有了该数据,就不会再添加了。
注意:这种方式新增数据的时候就不能再给主键Id赋值了,因为是先生成数据库,自动设置Id为主键,在添加数据的时候会自动赋值。
三、Data-Seeding本质
- 当调用HasData()方法首次迁移时,实质上是调用MigrationBuilder类中InsertData方法进行插入。
- 当调用HasData()方法更改数据(未更改主键)时,实质上是调用MigrationBuilder类中UpdateData方法进行更新操作。
- 当调用HasData()方法移除数据或更改主键时,实质上是调用MigrationBuilder类中DeleteData方法进行删除操作或者删除和更新操作。
1、首次迁移
我们在第一次执行完添加迁移命令以后,会生成一个迁移文件,如下图所示:
可以看到这时就是调用的InsertData方法来新增数据。
2、修改不是主键的数据
我们修改数据,将ef core修改为ef core 3.1.1,如下图所示:
修改完以后我们在执行迁移命令,如图所示:
这时在去看生成的迁移文件:
这时执行的就是UpdateData方法。
3、删除数据
接着我们把Id为1的数据在代码里面注释掉模拟删除操作,在执行迁移命令:
在看生成的迁移文件:
可以看到这次就是执行的DeleteData方法。
4、修改主键数据
我们在把Id为2的数据修改为6:
在执行迁移:
查看生成的迁移文件:
这次就是先执行DeleteData方法,然后在执行InsertData方法。
四、总结
针对种子数据初始化,主要有上面的三种方式,比较推荐的是第一种和第三种,可以根据自己的使用情况来选择适合自己的。