隐藏

EF core:Code First / 建库建表 / Migration

发布:2021/7/5 10:52:37作者:管理员 来源:本站 浏览次数:1646

Code Frist

要在C# code和数据库之间建立映射关系,EF提供了两种方式:

  • DB first:先在数据库上建表,然后根据数据库生成C#类(entity),适合于已有数据库结构的老项目
  • Code First:先声明C#类(entity),然后根据entity在数据库上建表,适合于新项目
因为要OOD(Object-Oriented Dessign/Develop),即从一开始就首先考虑对象(entity)而不是数据库,我们选择使用Code First。


entity(实体)

我们用entity来称呼所有需要被持久化的对象,比如:

 public class Student { public int Id { get; set; } //或者:public int StudentId { get; set; } public string Name { get; set; } }

注意:每一个Entity都应该有一个Key定义/辨别该Entity,EF默认使用属性名为 Id 或 类名+Id 的属性作为 Key。否则后面会报错。


DbContext和DbSet

这两个对象是EF的核心对象。使用他们首先需要引入相应的EF类库。使用NuGet添加:


开发人员需要自己创建一个类来继承DbContext

 public class SqlDbContext : DbContext //继承还是组合? { //注意这里:不能是字段,不能是字段,不能是字段啊! public DbSet<Student> Students { set; get; } //EF据此确定哪些class需要映射到数据库 }

然后,通过声明/添加DbSet<E>属性,告知EF哪些entity需要被持久化。


指定数据库

EF core需要在DbContext子类中override OnConfiguring()方法,指定数据库:

 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { string connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=17bang;Integrated Security=True;"; //UseSqlServer()需要添加NuGet引用:Microsoft.EntityFrameworkCore.SqlServer optionsBuilder.UseSqlServer(connectionString); }

EF通过provider的形式支持多种数据库,详见:Database Providers

为了适配SQL Server,还需要安装dll:

Microsoft.EntityFrameworkCore.SqlServer

事实上,如果愿意的话,你甚至可以使用自定义的provider来支持任意数据存储方式……


EF Migrations

可以根据DbContext中指定的entity,自动建表。

为了使用EF Migrations工具,我们还需要在起始项目中引入dll:(ASP.NET Core version 2.1后版本已自动安装)

Microsoft.EntityFrameworkCore.Tools

然后,在VS中打开 Package Manager Console(Tools > NuGet Package Manager > Package Manager Console)


测试是否已安装成功

输入:GET-HELP about_EntityFrameworkCore

显示以下内容,表明Migration Tool安装成功

如果操作系统是windows7,可能还需要额外的安装相应的.NET Framework/Core


Add-Migration

注意指定好:

  • 起始(startup)项目:默认是solution的启动项目,只能是“可执行”的
  • 目标(target)项目:默认是PM Console中的“Default Project”

Migration会:

  1. 从起始项目开始,
  2. 找到目标项目中的DbContext(应该有且仅有一个)
  3. 再找到该DbContext中包含的所有entity

将entity映射成相应的表结构。


Migration文件夹

成功运行命令:Add-Migration <name>之后(<name>由开发人员指定,注意要有意义),

EF在DbContext所在项目生成一个文件夹Migrations:

以及两种类文件:

  • migration文件
    1. 类名前缀是时间戳,显示migration生成时间(不是本地时间),有助于其后续维护
    2. 命令行中的<name>就对应着生成的类名(复习:部分类),里面有Up()和Down()两个方法

  • ModelSnapshot(快照):记录的是当前EF的映射情况

演示:读懂上述类文件代码


多次执行

Add-Migration可多次执行,比如随着项目的开发,Entity发生了变更
 //Student中添加一行Age public int Age { get; set; }

我们就可以再执行一次Add-Migration。

每执行一次Add-Migration,就会


  • 生成一个migration文件,其中的Up()和Down()方法内容,系由EF将现有的entity和ModelSnapShot.cs做对比确定
  • 更新ModelSnapShot所以它始终只有一个



Update-Database

仅仅执行Add-Migration,数据库不会变化(演示)

执行Update-Database命令,才会利用Migrations文件夹下内容,同步数据库表结构。

演示:检查数据库,可以看到

  • 根据entity类名生成的Student表,且列名和属性名相同
  • 表_MigrationHistory


_MigrationHistory表

该表由Migration自动生成并维护。

顾名思义,_MigrationHistory记录了和Migration的历史。

这样,当执行Update-Database命令时,EF会对比表_MigrationHistory和C#项目中的Migrations文件夹,决定需要执行的Migration



回滚/前进

项目开发中,我们会:

  • 不断的扩充Entity,比如
  • 甚至回退到某一个版本(复习:git)

所以,Migration被设计成可以

增量更新,不丢失数据

可以使用命令行和API完成




正是有了上述文件,在Update-Database时我们可以指定“版本号”,形成“回退/前进”效果:

  • 0:彻底revert所有migration
  • 不带参数:执行所有migration
  • 指定参数:Add-Migration中的<datetime_stamp><migration_name>(如果没有重复,也可以省略时间戳,简写为<migration_name>)

思考:为什么需要数据库里的_EFMigrationsHistory?


Remove-Migration

删除(不是revert)“最近”(注意只能是最近)的一次migration,表现为:<datetime_stamp><migration_name>.cs同时被删除

(演示:略)





每一次变更都可以需要Add-Migration和。

,数据库也应该进行相应的变更。

  • :属性/Set()方法演示:





Script-Migration

生成sql代码,常用于部署。可使用参数:

  • -From:从哪一次Migration开始,默认为第一次
  • -To:到哪一次结束,默认为最近一次
  • -Output:输出路径,默认在 程序运行 位置,如: /obj/Debug/netcoreapp2.1/ghbkztfz.sql

生成的SQL只包含_EFMigrationHistory数据。

演示:略


使用(C#)API

 //Database属性从何而来? var db = new DBUserRepository().Database; //类似于Update-Database: apply all pending migrations //本身不生成Migrations db.Migrate(); 

EnsureCreated()建立的数据库没有__EFMigrationsHistory表,所以不能再使用Migration。

API的好处是可以建库删库:

 //Enusure:存在才删除,不存在才创建 db.EnsureDeleted(); //Create数据库的同时建立表结构, db.EnsureCreated();



 public Student Save(Student student) { Students.Add(student); SaveChanges(); return student; } public Student GetByName(string name) { return Students .Where(u => u.Name == name) .SingleOrDefault(); }








作业

将User类映射到数据库:

  1. 使用EF的API直接建库建表删库
  2. 使用Migration工具建库建表

Migration之后,在User类上添加一列:int FailedTry(尝试登陆失败次数),使用Migration工具:

  1. 将改动同步到数据库
  2. 回退数据库到FailedTry添加之前