zoukankan      html  css  js  c++  java
  • Entity Framework(六):数据迁移

           在前面的几篇文章中,简单的介绍了如何使用Entity Framework的Code First模式创建数据库,但是,在前面的几篇文章中,我们都是通过使用数据库初始化策略来做,也就是每次先删除数据库然后在创建,这样才能把新增加的字段信息更新到数据库,在测试的时候可以做,但是在正式的生产环境中就不能使用这种方式了,那么我们如何做才能在原有的数据库基础上进行字段的增删,这就需要使用到EF的数据迁移技术,使用EF的数据迁移技术,我们不用每次都先删除数据库然后在创建,并且数据库迁移技术还可以为我们设置初始化的数据。在这篇文章中,将会演示“在实体类发生改变时如何自动更新数据库中的表结构”
    一、合并和迁移
    1、合并

    合并是指“新的实体模型映射到数据库中,更新其结构”,例如:
    新增了实体类,表现在数据库中就是新增加实体类对应的数据表。
    删除了实体类,表现在数据库中就是删除了实体类对应的数据表。
    在一个已经存在的实体类中增加属性,表现在数据库中就是在实体类对应的数据表中新增加字段。
    在一个已经存在的实体类中删除属性,表现在数据库中就是在实体类对应的数据表中删除字段。
    修改一个已经存在的实体类中属性的名称或类型,表现在数据库中就是修改实体类对应的数据表中字段的名称或类型。
    2、迁移

    迁移是指“在更新数据库结构时,把老结构中的数据迁移到新结构中”。

    二、迁移前的准备工作

    搭建项目结构,整体的项目结构包括一个控制台应用程序和两个类库,项目结构如下:

    其中EF.Application是控制台程序,EF.FluentAPI和EF.Model是类型,EF.Model里面存的是实体类,EF.FluentAPI是实体类的Map类,用来设置FluentAPI。

    Student类结构如下:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace EF.Model
     8 {
     9     public class Student
    10     {
    11         public int StudentID { get; set; }
    12 
    13         public string StudentName { get; set; }
    14 
    15         public int Age { get; set; }
    16 
    17         public string Sex { get; set; }
    18     }
    19 }

     StudentMap类结构如下:

     1 using EF.Model;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.ComponentModel.DataAnnotations.Schema;
     5 using System.Data.Entity.ModelConfiguration;
     6 using System.Linq;
     7 using System.Text;
     8 using System.Threading.Tasks;
     9 
    10 namespace EF.FluentAPI
    11 {
    12     /// <summary>
    13     /// 使用FluentAPI配置
    14     /// </summary>
    15    public class StudentMap :EntityTypeConfiguration<Student>
    16     {
    17        public StudentMap()
    18        {
    19            // 配置数据库中生成的表的名称
    20            this.ToTable("Students");
    21            // 设置StudentID列自动增长
    22            this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    23            // 设置StudentID列作为主键
    24            this.HasKey(p => p.StudentID);
    25            // 设置StudentName列的类型是nvarchar,最大长度是50,必须的
    26            this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
    27            // 设置Age列是必须的
    28            this.Property(p => p.Age).IsRequired();
    29            //  设置Sex的类型是nvarchar
    30            this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
    31        }
    32     }
    33 }

     EF上下文类结构:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Data.Entity;
     4 using System.Linq;
     5 using System.Text;
     6 using System.Threading.Tasks;
     7 
     8 namespace EF.FluentAPI
     9 {
    10    public class EFDbContext:DbContext
    11     {
    12        public EFDbContext()
    13            : base("name=CodeFirstApplication")
    14        { }
    15 
    16        protected override void OnModelCreating(DbModelBuilder modelBuilder)
    17        {
    18            modelBuilder.Configurations.Add(new StudentMap());
    19        }
    20     }
    21 }

     数据库连接字符串:【注意:在EF.Application和EF.FluentAPI的App.config里面都要添加上该连接字符串】

    1 <connectionStrings>
    2     <add name="CodeFirstApplication" connectionString="Server=.;Database=MigrationsDB;User Id=sa;Password=1qaz@WSX" providerName="System.Data.SqlClient"/>
    3 </connectionStrings>

     控制台程序:

     1 using EF.FluentAPI;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Text;
     6 using System.Threading.Tasks;
     7 using EF.Model;
     8 
     9 namespace EF.Application
    10 {
    11     class Program
    12     {
    13         static void Main(string[] args)
    14         {
    15             Console.WriteLine("请输入学生姓名:");
    16             string studentName = Console.ReadLine().Trim();
    17             Console.WriteLine("请输入学生年龄:");
    18             int age = 0;
    19             if (!int.TryParse(Console.ReadLine().Trim(), out age))
    20             {
    21                 Console.WriteLine("年龄只能输入正整数,请重新输入:");
    22                 return;
    23             }
    24             Console.WriteLine("请输入学生性别(男/女):");
    25             string sex = Console.ReadLine().Trim();
    26 
    27             using (var context = new EFDbContext())
    28             {
    29                 Student student = new Student() 
    30                 {
    31                 StudentName=studentName,
    32                 Age=age,
    33                 Sex=sex
    34                 };
    35 
    36                 context.Entry(student).State = System.Data.Entity.EntityState.Added;
    37                 // 保存
    38                 context.SaveChanges();
    39             }
    40 
    41             Console.Write("添加成功");
    42             Console.ReadKey();
    43         }
    44     }
    45 }

     运行程序:

    查看数据库:

    其中生成的表__MigrationHistory用来记录每次的迁移。

    三、迁移

    现在我们在Student实体类中增加Grade字段,整体项目做如下的改动:

    Student类:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace EF.Model
     8 {
     9     public class Student
    10     {
    11         public int StudentID { get; set; }
    12 
    13         public string StudentName { get; set; }
    14 
    15         public int Age { get; set; }
    16 
    17         public string Sex { get; set; }
    18 
    19         // 新增加Grade字段,用来实现数据迁移
    20         public string Grade { get; set; }
    21     }
    22 }

     StudentMap类:

     1 using EF.Model;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.ComponentModel.DataAnnotations.Schema;
     5 using System.Data.Entity.ModelConfiguration;
     6 using System.Linq;
     7 using System.Text;
     8 using System.Threading.Tasks;
     9 
    10 namespace EF.FluentAPI
    11 {
    12     /// <summary>
    13     /// 使用FluentAPI配置
    14     /// </summary>
    15    public class StudentMap :EntityTypeConfiguration<Student>
    16     {
    17        public StudentMap()
    18        {
    19            // 配置数据库中生成的表的名称
    20            this.ToTable("Students");
    21            // 设置StudentID列自动增长
    22            this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    23            // 设置StudentID列作为主键
    24            this.HasKey(p => p.StudentID);
    25            // 设置StudentName列的类型是nvarchar,最大长度是50,必须的
    26            this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
    27            // 设置Age列是必须的
    28            this.Property(p => p.Age).IsRequired();
    29            //  设置Sex的类型是nvarchar
    30            this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
    31            // 设置Grade字段是必须的
    32            this.Property(p => p.Grade).HasColumnType("varchar").HasMaxLength(16).IsRequired();
    33        }
    34     }
    35 }

     控制台:

     1 using EF.FluentAPI;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Text;
     6 using System.Threading.Tasks;
     7 using EF.Model;
     8 
     9 namespace EF.Application
    10 {
    11     class Program
    12     {
    13         static void Main(string[] args)
    14         {
    15             Console.WriteLine("请输入学生姓名:");
    16             string studentName = Console.ReadLine().Trim();
    17             Console.WriteLine("请输入学生年龄:");
    18             int age = 0;
    19             if (!int.TryParse(Console.ReadLine().Trim(), out age))
    20             {
    21                 Console.WriteLine("年龄只能输入正整数,请重新输入:");
    22                 return;
    23             }
    24             Console.WriteLine("请输入学生性别(男/女):");
    25             string sex = Console.ReadLine().Trim();
    26             Console.WriteLine("请输入年级:");
    27             string grade = Console.ReadLine().Trim();
    28 
    29             using (var context = new EFDbContext())
    30             {
    31                 Student student = new Student() 
    32                 {
    33                 StudentName=studentName,
    34                 Age=age,
    35                 Sex=sex,
    36                 Grade=grade
    37                 };
    38 
    39                 context.Entry(student).State = System.Data.Entity.EntityState.Added;
    40                 // 保存
    41                 context.SaveChanges();
    42             }
    43 
    44             Console.Write("添加成功");
    45             Console.ReadKey();
    46         }
    47     }
    48 }

     启用数据迁移:

    1、打开迁移

    在程序包管理器控制台中输入:Enable-Migrations

    按回车键后,会生成Migrations文件夹,以及Migrations文件夹下面的Configuration类和201711281316287_InitialCreate类:

    Configuration:这个类允许你去配置如何迁移,对于本文将使用默认的配置(在本文中因为只有一个Context,Enable-Migrations将自动对context type作出适配);

    201711281316287_InitialCreate:这个迁移之所以存在是因为我们之前用Code First创建了数据库,在启用迁移之前,scaffolded migration里面的代码表示在数据库中已经创建的对象,本文中即为表Students。

    Code First Migrations有两个需要熟悉的命令:
    Add-Migration 将scaffold创建下一次基于上一次迁移以来的更改的迁移;
    Update-Database 将任何挂起的迁移应用到数据库;

    以上面新增加的字段Grade属性为例,命令Add-Migration允许我们对迁移进行命名,我们把迁移命名为AddGrade。

    2、增加迁移节点

    在程序包管理器控制台中输入命令:Add-Migration AddGrade

    一个新的迁移(201711281402492_AddGrade)在目录Migrations中创建成功:

    201711281402492_AddGrade类结构如下:

     1 namespace EF.FluentAPI.Migrations
     2 {
     3     using System;
     4     using System.Data.Entity.Migrations;
     5     
     6     public partial class AddGrade : DbMigration
     7     {
     8         public override void Up()
     9         {
    10             AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: 16, unicode: false));
    11         }
    12         
    13         public override void Down()
    14         {
    15             DropColumn("dbo.Students", "Grade");
    16         }
    17     }
    18 }

     201711281402492_AddGrade的名称是上面Add后面定义的迁移名称,而类下面有两个方法:一个是Up,一个是Down,记录了需要升级的修改,这里也就是Students表增加了Grade列。只要我们在后面执行Update-Database,就会执行此类下面的Up函数。

    这里的Down函数简单介绍就是:为了回滚修改而设计的。如果用户希望恢复到某一个迁移节点,程序会自动根据已经执行的迁移,判断回滚哪些迁移,执行他们的Down函数。

    3、更新数据库

    在程序包管理器控制台中输入命令:Update-Database -Verbose

    查看数据库,Students表已经增加Grade字段:

    到此为止,迁移已经完成,再次运行项目:

    查看数据库:

    输入的数据保存到数据库中。

    四、修改属性

    1、将Student实体类中的Grade属性的名称修改为GradeTest:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace EF.Model
     8 {
     9     public class Student
    10     {
    11         public int StudentID { get; set; }
    12 
    13         public string StudentName { get; set; }
    14 
    15         public int Age { get; set; }
    16 
    17         public string Sex { get; set; }
    18 
    19         // 将Grade属性名修改为GradeTest
    20         public string GradeTest { get; set; }
    21     }
    22 }

     2、增加迁移节点

    在程序包管理器控制台中输入命令:Add-Migration ModifyGrade

    在Migrations文件夹下面会生成本次的迁移记录:201711290052153_ModifyGrade

    查看201711290052153_ModifyGrade类结构:

     1 namespace EF.FluentAPI.Migrations
     2 {
     3     using System;
     4     using System.Data.Entity.Migrations;
     5     
     6     public partial class ModifyGrade : DbMigration
     7     {
     8         public override void Up()
     9         {
    10             AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: 16, unicode: false));
    11             DropColumn("dbo.Students", "Grade");
    12         }
    13         
    14         public override void Down()
    15         {
    16             AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: 16, unicode: false));
    17             DropColumn("dbo.Students", "GradeTest");
    18         }
    19     }
    20 }

     可以看到在Up方法里面,它不是直接修改了列的名称,而是先增加了一个新列GradeTest,然后删除旧列Grade。这样执行会有一个后果:如果Grade列里面有数据,数据会全部丢失。

    3、更新到数据库

    在程序包管理器控制台中输入命令:Update-Database -Verbose

    查看数据库表:

    通过查看数据库表,会发现新增加了GradeTest列,原先的Grade列被删掉,数据也全部丢失。

    五、删除属性

    删除属性和增加属性的操作差不多

    1、修改Student实体类,注释掉GradeTest属性:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace EF.Model
     8 {
     9     public class Student
    10     {
    11         public int StudentID { get; set; }
    12 
    13         public string StudentName { get; set; }
    14 
    15         public int Age { get; set; }
    16 
    17         public string Sex { get; set; }
    18 
    19         // 将GradeTest属性删除
    20         //public string GradeTest { get; set; }
    21     }
    22 }

     2、增加迁移节点

    在程序包管理器控制台中输入命令:Add-Migration DeleteGradeTest

    查看生成的迁移记录类:

    201711290130110_DeleteGradeTest类里面的Up方法里面删除了GradeTest列。

    3、更新到数据库

    在程序包管理器控制台中输入命令:Update-Database -Verbose

    查看数据库表,可以发现GradeTest列被删除掉:

    六、迁移至指定的版本(包括后退)

    到目前为止,我们进行迁移都是进行升级,但是有些时候我们需要升级或降级至指定版本,例如我们想迁移数据库至运行ModifyGrade迁移之后的状态,此时我们就可以使用-TargetMigration来降级到这个版本。

    在程序包管理器控制台中输入命令:Update-Database -TargetMigration:ModifyGrade

    这个命令将会运行201711290130110_DeleteGradeTest类里面的Down命令。Reverting migrations表示回复迁移。

    这时候在查看数据库表,会发现Students表中又有了GradeTest列。

    如果你想回滚一切至空数据库,可以使用命令:Update-Database -TargetMigration:$InitialDatabase

    这时候在查看数据库,发现Students表中所有列都已经被删除:

    七、如何在保留现有数据的基础上修改列名

    查看DbMigration类,会发现该类下面有一个RenameColumn()的方法,使用该方法可以在不丢失数据的基础上修改列的名称:

    1、修改Student实体类,将StudentName修改为Name。

    2、在程序包管理器控制台中输入命令:Add-Migration RenameStudentName,生成迁移文件,手动修改迁移类文件,修改内容如下:

     1 namespace EF.FluentAPI.Migrations
     2 {
     3     using System;
     4     using System.Data.Entity.Migrations;
     5     
     6     public partial class RenameStudentName : DbMigration
     7     {
     8         public override void Up()
     9         {
    10             //AddColumn("dbo.Students", "Name", c => c.String(nullable: false, maxLength: 50));
    11             AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: 16, unicode: false));
    12             //DropColumn("dbo.Students", "StudentName");
    13             RenameColumn("dbo.Students", "StudentName", "Name");
    14         }
    15         
    16         public override void Down()
    17         {
    18             //AddColumn("dbo.Students", "StudentName", c => c.String(nullable: false, maxLength: 50));
    19             DropColumn("dbo.Students", "GradeTest");
    20             //DropColumn("dbo.Students", "Name");
    21             RenameColumn("dbo.Students", "Name", "StudentName");
    22         }
    23     }
    24 }

     3、执行Update-Database命令,数据库列名被自动修改。

    这里值得注意的是:在执行Update命令时,程序会提醒操作者: Changing any part of an object name could break scripts and stored procedures。翻译为中文:更改对象名的任一部分都可能会破坏脚本和存储过程。及修改列名可能会导致存储过程及其他调用列的sql脚本失效。

    查看数据库表发现列名已经修改:

    注意:在实际开发中,不建议随便修改列名:可能会导致其他用的该列的地方调用失败。

    总结:

    1、迁移的关联在数据库的迁移历史表__MigrationHistory和项目的Migrations文件夹下的继承了DbMigration的cs文件。

    2、Migrations文件夹下的继承了DbMigration的cs文件可以手动修改,这里的修改可以非常灵活,表格和表格字段的增删改,在这里都有。

    代码下载地址:https://pan.baidu.com/s/1eR9RJ0Y

  • 相关阅读:
    辨异 —— 冠词(定冠词、不定冠词、零冠词)
    辨异 —— 冠词(定冠词、不定冠词、零冠词)
    dot 语法全介绍
    dot 语法全介绍
    图像的简单认识
    图像的简单认识
    向量点乘(内积)和叉乘(外积、向量积)概念及几何意义解读
    图的重要性质
    Android下载文件提示文件不存在。。。 java.io.FileNotFoundException
    Java程序猿的JavaScript学习笔记(5——prototype和Object内置方法)
  • 原文地址:https://www.cnblogs.com/dotnet261010/p/7912818.html
Copyright © 2011-2022 走看看