目录

EF-CodeFirst-数据库生成更新及回滚

EF CodeFirst 数据库生成、更新及回滚

说明:

1、本文描述一种使用EF的CodeFirst方式生成、更新及回滚数据库的操作方法,要求:

【1】更新数据库的情况下,希望原有数据库的数据能够尽可能保留;

【2】每次实体模型的变更信息能够保留,并能据此回滚数据库;

2、“ 补充说明 ”补充了“ 操作方法 ”中涉及的相关知识点的说明;

EF CodeFirst 数据库生成、更新及回滚

【1】操作方法

【1.1】生成数据库

  • 1、引入EF框架

    在VS中, 工具 -> NuGet包管理器 -> 管理解决方案的NuGet程序包 ,打开如下界面,搜索“EntityFramework”,将其安装到需要EF的项目中。

    https://i-blog.csdnimg.cn/blog_migrate/908dc2f5df9ba6aaa7762c8902cbdb63.png

  • 2、添加 连接字符串

    将连接字符串添加到 启动项目 的App.config文件中,如下所示。

    https://i-blog.csdnimg.cn/blog_migrate/f54c0134ac32a52de257edba39f4e70d.png

Data Source=.;Initial Catalog=Demo_DB; User Id=sa;Password=123456

// Data Source=.				本地数据库
// Initial Catalog=Demo_DB		数据库名称为Demo_DB
// User Id=sa					使用数据库的sa账户登录数据库
// Password=123456				sa账户密码为123456
  • 3、实现DbContext类的派生类
class MyDbContext : DbContext
{
    public MyDbContext() : base("name=conStr")
    {
        // 设置数据库初始化器
    }

    // 定义实体类的 DbSet

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // 注册Mapping类
    }
}

base(“name=conStr”) 代表将name等于"conStr"的连接字符串指向的数据库 作为 目标数据库。

  • 4、生成Configuration类

    在“程序包管理器控制台”中输入 enable-migrations 命令,如下图所示。

    注意, 默认项目 要选择正确。

    https://i-blog.csdnimg.cn/blog_migrate/1d3359baa3ff5c9e148ea2b774d983d5.png

    命令执行后如下所示,并自动生成 Migrations 文件夹及 Configuration.cs 文件。

    https://i-blog.csdnimg.cn/blog_migrate/53634dc3bd78cec49c811e73b234b5ea.png

    https://i-blog.csdnimg.cn/blog_migrate/221c9754cf6eb3c35ecf0d950e2d6a09.png

    https://i-blog.csdnimg.cn/blog_migrate/a02c3d31aa95a705225f1d0d50511dbf.png

  • 5、在DbContext类的派生类添加初始化器

public MyDbContext() : base("name=conStr")
{
    // 设置数据库初始化器
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
}

初始化器 代表依据实体类生成数据库的策略,MigrateDatabaseToLatestVersion代表在更新数据库时尽可能不删除原有数据库数据的策略。

  • 6、添加实体类及其Mapping类

    此处以添加一个Student类为例(对应数据库Student表)。

    Student类及StudentMapping类如下:

    https://i-blog.csdnimg.cn/blog_migrate/0f0a9b56c8798b6b5bbbc0c537dfa2a5.png

class Student
{
    public int Id { get; set; }

    public string Name { get; set; }
}
class StudentMapping : EntityTypeConfiguration<Student>
{
    public StudentMapping()
    {
        // 定义表名
        this.ToTable("Student");

        // 定义主键
        this.HasKey(s => s.Id);

        // 定义各属性映射方式
        this.Property(s => s.Id).HasColumnName("Id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
        this.Property(s => s.Name).HasColumnName("Name").HasColumnType(SqlDbType.NVarChar.ToString()).HasMaxLength(50).IsRequired();
    }
}

StudentMapping类 定义了Student实体类到数据库Student表的映射规则

  • 7、在MyDbContext类中注册实体类及其Mapping类
class MyDbContext : DbContext
{
    public MyDbContext() : base("name=conStr")
    {
        // 设置数据库初始化器
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
    }

    // 定义实体类的 DbSet
    public DbSet<Student> Students { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // 注册Mapping类
        modelBuilder.Configurations.Add(new StudentMapping());
    }
}

Students属性 代表整张Student表中的数据。

  • 7、生成迁移类

    在“程序包管理器控制台”中使用 add-migration [ name ] 命令,[ name ]为要生成的迁移类名,此处使用“InitialCreate”,如下图。

    https://i-blog.csdnimg.cn/blog_migrate/ba3678e83a396ed1fa06a9df9fd2a156.png

    执行成功后,如下图所示,并在 Migrations 文件夹下自动生成带日期信息的迁移文件(此时数据库还未生成)。

    https://i-blog.csdnimg.cn/blog_migrate/b52562e7408b63b441358d321d1b1dcd.png

迁移类 中只有 UpDown 方法,分别用于对数据库的更新和回滚:

Up方法 用于更新,将当前数据库更新到本迁移类对应的实体模型的状态;

Down方法 用于回滚,将当前数据库回滚到应用本迁移类之前对应的实体模型的状态;

  • 8、使用迁移类生成数据库

    在“程序包管理器控制台”中使用 update-database 命令,如下所示。

    https://i-blog.csdnimg.cn/blog_migrate/f92fda695b33c649eab63bffa485d3cb.png

    执行成功后,如下图所示。

    https://i-blog.csdnimg.cn/blog_migrate/278fa5f9f462cd630ecc26cf9992bf9c.png

    查看数据库,Student数据表已生成。

    https://i-blog.csdnimg.cn/blog_migrate/16636e220d3ba939ed278c44981b86db.png

【1.2】更新数据库

举例说明: Student表原有两条数据如下图所示,向Student实体类增加一个Age字段后,更新数据库。

https://i-blog.csdnimg.cn/blog_migrate/13fa4d181c781bf07e62c6d6e776fb0d.png

  • 1、修改实体类及Mapping文件
class Student
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }
}
class StudentMapping : EntityTypeConfiguration<Student>
{
    public StudentMapping()
    {
        // 定义表名
        this.ToTable("Student");

        // 定义主键
        this.HasKey(s => s.Id);

        // 定义各属性映射方式
        this.Property(s => s.Id).HasColumnName("Id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
        this.Property(s => s.Name).HasColumnName("Name").HasColumnType(SqlDbType.NVarChar.ToString()).HasMaxLength(50).IsRequired();
        this.Property(s => s.Age).HasColumnName("Age").HasColumnType(SqlDbType.Int.ToString()).IsRequired();
    }
}
  • 2、生成迁移类

    在“程序包管理器控制台”中使用 add-migration [ name ] 命令,生成新的迁移类,如下所示。

    https://i-blog.csdnimg.cn/blog_migrate/0ad09385442660fb5e20240437d9e15a.png

    https://i-blog.csdnimg.cn/blog_migrate/d500eaa265fe13fd149fb3e514e381a3.png

    https://i-blog.csdnimg.cn/blog_migrate/dce4e394d237fceadb96b5d90f9ffc02.png

  • 3、使用迁移类更新数据库

    在“程序包管理器控制台”中使用 update-database 命令,如下所示。

    https://i-blog.csdnimg.cn/blog_migrate/d4c83758097ab2912017a950c13e7b06.png

    查看数据库,Student表已更新,且原有数据仍在。

    https://i-blog.csdnimg.cn/blog_migrate/f3d940202cbf2ecb454cf709bbb6fec0.png

【1.3】回滚数据库

举例说明: 希望将数据库回滚到 InitialCreate迁移类 对应的实体模型状态时期的版本。

  • 在“程序包管理器控制台”中使用 update-database -TargetMigration: [name] 命令,如下所示。

    https://i-blog.csdnimg.cn/blog_migrate/30742c4da2d71360e84b647b0eef2fc5.png

    执行成功,如下图所示。

    https://i-blog.csdnimg.cn/blog_migrate/40cf7ac86c8bbafa09c8498d70a6177d.png

    数据库也退回之前的表结构,且数据保留。

    https://i-blog.csdnimg.cn/blog_migrate/c3a3227f5aa0c126b85a9bc8b5be9d3d.png

【2】补充说明

【2.1】EF框架

  • NuGet介绍及使用,参考:

  • EF框架安装后引入的程序集:

    https://i-blog.csdnimg.cn/blog_migrate/3313df9aef940813f157196dd8fbffa1.png

    本文内容仅使用到了EntityFramework.dll。

【2.2】数据库连接配置

  • 【1.1】生成数据库 中,步骤2、3为配置 数据库连接 的过程,包括此方法在内,有如下几种方式:

    1. 连接字符串name与DbContext类的派生类同名

    https://i-blog.csdnimg.cn/blog_migrate/910f00ea34c7bd97c040c305c20a45fb.png

    https://i-blog.csdnimg.cn/blog_migrate/e022798a64e2e62ef02576693958cf02.png

    2. 在DbContext类的派生类的构造函数中传入连接字符串的name (本文方法)

    https://i-blog.csdnimg.cn/blog_migrate/73acb26260c5dd2d3355cbdc62cfc630.png

    https://i-blog.csdnimg.cn/blog_migrate/780e9e1664cbc7177ebcf4178fbed3fe.png

    3. 在DbContext类的派生类的构造函数中传入数据库名

    此种方法不使用连接字符串;

    似乎只能在VS的Local SQL Server中生成数据库;

    暂不介绍;

【2.3】数据库初始化器

  • 【1.1】生成数据库 中,步骤5为添加 数据库初始化器 的方法。

  • 内置4种初始化器,如下:

    1.CreateDatabaseIfNotExists (EF默认)

    特点

    1)数据库不存在,创建数据库;

    2)数据库存在,不创建数据库;

    3)存在的数据库与实体类模型不符,抛出异常;

    使用

    Database.SetInitializer(new CreateDatabaseIfNotExists<MyDbContext>());

    2.DropCreateDatabaseIfModelChanges

    特点

    1)当实体类模型改变时,删除原有数据库,重新创建数据库;

    使用

    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyDbContext>());

    3.DropCreateDatabaseAlways

    特点

    1)每次运行都删除原有数据库,重新创建数据库;

    使用

    Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());

    4.MigrateDatabaseToLatestVersion (本文使用的)

1、2、3中的初始化器,不需要 【1.1】生成数据库 步骤4中生成的 Configuration 类(也可以使用);

所以,也不能使用 update-database 命令生成数据库;

要生成数据库,可以运行程序,执行到 创建DbContext派生类对象时 即会生成;

2、3中的初始化器,因为要删除原有数据库,所以可能会抛出“无法删除数据库,因为数据库正在使用的异常”,这多半是SQL Server Management Studio连接了数据库并进行了一些操作导致的,断开连接即可;

  • 自定义初始化器:可以继承上述初始化器,创建自定义初始化器,重写Seed方法添加初始数据等。

【2.4】自动迁移和代码迁移

  • 【1.1】生成数据库 中,步骤4、5为配置 数据库迁移 的方法,本文使用的是 代码迁移 ,还有一种方式叫 自动迁移

  • 两种迁移均使用 MigrateDatabaseToLatestVersion 数据库初始化器。

  • 介绍两种迁移:

    1. 自动迁移

    在“程序包管理器控制台”中输入 enable-migrations -EnableAutomaticMigration:$true 命令,生成Configuration类如下:

    https://i-blog.csdnimg.cn/blog_migrate/2f9b4f018fbc9f9489116e04d92e345d.png

    在“程序包管理器控制台”中输入 update-database 命令,执行后如下图所示,并且不会生成代码迁移中带日期信息的迁移类。

    https://i-blog.csdnimg.cn/blog_migrate/e5e98dbfbc16b94db79b267455f977dc.png

    https://i-blog.csdnimg.cn/blog_migrate/4d4e4ead2566ef4388739bd97029f341.png

    1. 代码迁移

    【1.1】生成数据库 中,步骤4、5所述。

【2.5】__MigrationHistory表

  • __MigrationHistory表是EF创建数据库时自动生成的表;

  • 该表中存储当前数据库对应的实体模型版本的相关信息,例如下图:

    https://i-blog.csdnimg.cn/blog_migrate/615d127cb95db04f167f7d07dd904631.png

    MigrationId
    带时间戳的实体模型版本名;
    ContextKey
    实体类模型的组名,一个组对应一个项目(仅用于多个项目共享一个数据库的情况);
    Model
    此版本实体类模型的二进制信息;
    ProductVersion
    EF的版本号;
  • ContextKey 可以在Configuration类中手动指定,如下图。

    https://i-blog.csdnimg.cn/blog_migrate/9273f49fd6401561546ea30e64eb14dc.png

    https://i-blog.csdnimg.cn/blog_migrate/454aad62a64020f888e0d0e07945b945.png

【2.6】自动迁移的更新及回滚

更新

  • 【2.4】 中介绍的 自动迁移 ,生成数据库没有问题,但是更新存在问题。

  • 只是添加实体类的更新也没有问题,但如果要修改原有实体类的属性时,就会抛出异常,此时可以在 Configuration类 加入 AutomaticMigrationDataLossAllowed = true 解决此问题,如下图。

    https://i-blog.csdnimg.cn/blog_migrate/90b3b6a93f9ac20c038ef0bcbb31571f.png

  • AutomaticMigrationDataLossAllowed = true 代表允许迁移的时候产生数据丢失。

回滚

  • 自动迁移 也可以实现回滚操作:

    在数据库的__MigrationHistory表中找到要回滚到的版本,如下图选中项。

    https://i-blog.csdnimg.cn/blog_migrate/61a7aab7d20b6fe58a71ed894ff96f5b.png

    在“程序包管理器控制台”中使用 update-database -TargetMigration: [name] 命令,其中 [name] 要使用带时间戳的全名,如下图所示。

    https://i-blog.csdnimg.cn/blog_migrate/87ee24dfab41fd635304b32e6b1263c9.png

    执行成功,如下图所示。

    https://i-blog.csdnimg.cn/blog_migrate/34d026afea66d56f9d61865085c63d6f.png

    数据库的__MigrationHistory表也表明退回了版本,如下。

    https://i-blog.csdnimg.cn/blog_migrate/ed8b06a23b76cd0c14a1c57ca138a021.png

【2.7】三个命令

enable-migrations

  • 用于自动迁移或代码迁移时生成Configuration类;
  • 通过 “-EnableAutomaticMigration:$true” 参数控制是自动迁移还是代码迁移;
    • 自动迁移

      https://i-blog.csdnimg.cn/blog_migrate/a63e166a1c96f8a6dce2e6d9eb3a82fa.png

    • 代码迁移

      https://i-blog.csdnimg.cn/blog_migrate/a6c8c03964c30fb434ecb3656ade124e.png

  • 如果已存在的目标数据库中__MigrationHistory表的ContextKey与enable-migrations命令默认的ContextKey不相同,则会在Configuration类的构造函数中生成ContextKey,同时生成当前数据库实体类模型的迁移类文件;

add-migration [name]

  • 用于生成当前实体类模型的迁移类文件;
  • (代码迁移)如果当前最新的迁移类文件,还没有通过update-database命令更新到数据库,则不能通过add-migration [name]命令生成更新的迁移类文件;

update-database

  • 用于按照迁移策略更新数据库;
  • (代码迁移)最新的实体类模型在未使用add-migration [name]命令生成其迁移类时,无法更新数据库;
  • (代码迁移)如果要删除某迁移类文件,最好先将数据库回滚到此迁移的上一版本,再删除;