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

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

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

二、迁移前的准备工作

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

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

Student类结构如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Model
{
public class Student
{
public int StudentID { get; set; } public string StudentName { get; set; } public int Age { get; set; } public string Sex { get; set; }
}
}

StudentMap类结构如下:

 using EF.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.FluentAPI
{
/// <summary>
/// 使用FluentAPI配置
/// </summary>
public class StudentMap :EntityTypeConfiguration<Student>
{
public StudentMap()
{
// 配置数据库中生成的表的名称
this.ToTable("Students");
// 设置StudentID列自动增长
this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// 设置StudentID列作为主键
this.HasKey(p => p.StudentID);
// 设置StudentName列的类型是nvarchar,最大长度是50,必须的
this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength().IsRequired();
// 设置Age列是必须的
this.Property(p => p.Age).IsRequired();
// 设置Sex的类型是nvarchar
this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
}
}
}

EF上下文类结构:

 using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.FluentAPI
{
public class EFDbContext:DbContext
{
public EFDbContext()
: base("name=CodeFirstApplication")
{ } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new StudentMap());
}
}
}

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

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

控制台程序:

 using EF.FluentAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EF.Model; namespace EF.Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入学生姓名:");
string studentName = Console.ReadLine().Trim();
Console.WriteLine("请输入学生年龄:");
int age = ;
if (!int.TryParse(Console.ReadLine().Trim(), out age))
{
Console.WriteLine("年龄只能输入正整数,请重新输入:");
return;
}
Console.WriteLine("请输入学生性别(男/女):");
string sex = Console.ReadLine().Trim(); using (var context = new EFDbContext())
{
Student student = new Student()
{
StudentName=studentName,
Age=age,
Sex=sex
}; context.Entry(student).State = System.Data.Entity.EntityState.Added;
// 保存
context.SaveChanges();
} Console.Write("添加成功");
Console.ReadKey();
}
}
}

运行程序:

查看数据库:

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

三、迁移

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

Student类:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Model
{
public class Student
{
public int StudentID { get; set; } public string StudentName { get; set; } public int Age { get; set; } public string Sex { get; set; } // 新增加Grade字段,用来实现数据迁移
public string Grade { get; set; }
}
}

StudentMap类:

 using EF.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.FluentAPI
{
/// <summary>
/// 使用FluentAPI配置
/// </summary>
public class StudentMap :EntityTypeConfiguration<Student>
{
public StudentMap()
{
// 配置数据库中生成的表的名称
this.ToTable("Students");
// 设置StudentID列自动增长
this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// 设置StudentID列作为主键
this.HasKey(p => p.StudentID);
// 设置StudentName列的类型是nvarchar,最大长度是50,必须的
this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength().IsRequired();
// 设置Age列是必须的
this.Property(p => p.Age).IsRequired();
// 设置Sex的类型是nvarchar
this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
// 设置Grade字段是必须的
this.Property(p => p.Grade).HasColumnType("varchar").HasMaxLength(16).IsRequired();
}
}
}

控制台:

 using EF.FluentAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EF.Model; namespace EF.Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入学生姓名:");
string studentName = Console.ReadLine().Trim();
Console.WriteLine("请输入学生年龄:");
int age = ;
if (!int.TryParse(Console.ReadLine().Trim(), out age))
{
Console.WriteLine("年龄只能输入正整数,请重新输入:");
return;
}
Console.WriteLine("请输入学生性别(男/女):");
string sex = Console.ReadLine().Trim();
Console.WriteLine("请输入年级:");
string grade = Console.ReadLine().Trim(); using (var context = new EFDbContext())
{
Student student = new Student()
{
StudentName=studentName,
Age=age,
Sex=sex,
Grade=grade
}; context.Entry(student).State = System.Data.Entity.EntityState.Added;
// 保存
context.SaveChanges();
} Console.Write("添加成功");
Console.ReadKey();
}
}
}

启用数据迁移:

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类结构如下:

 namespace EF.FluentAPI.Migrations
{
using System;
using System.Data.Entity.Migrations; public partial class AddGrade : DbMigration
{
public override void Up()
{
AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: , unicode: false));
} public override void Down()
{
DropColumn("dbo.Students", "Grade");
}
}
}

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

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

3、更新数据库

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

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

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

查看数据库:

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

四、修改属性

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Model
{
public class Student
{
public int StudentID { get; set; } public string StudentName { get; set; } public int Age { get; set; } public string Sex { get; set; } // 将Grade属性名修改为GradeTest
public string GradeTest { get; set; }
}
}

2、增加迁移节点

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

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

查看201711290052153_ModifyGrade类结构:

 namespace EF.FluentAPI.Migrations
{
using System;
using System.Data.Entity.Migrations; public partial class ModifyGrade : DbMigration
{
public override void Up()
{
AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: , unicode: false));
DropColumn("dbo.Students", "Grade");
} public override void Down()
{
AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: , unicode: false));
DropColumn("dbo.Students", "GradeTest");
}
}
}

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

3、更新到数据库

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

查看数据库表:

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

五、删除属性

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

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Model
{
public class Student
{
public int StudentID { get; set; } public string StudentName { get; set; } public int Age { get; set; } public string Sex { get; set; } // 将GradeTest属性删除
//public string GradeTest { get; set; }
}
}

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,生成迁移文件,手动修改迁移类文件,修改内容如下:

 namespace EF.FluentAPI.Migrations
{
using System;
using System.Data.Entity.Migrations; public partial class RenameStudentName : DbMigration
{
public override void Up()
{
//AddColumn("dbo.Students", "Name", c => c.String(nullable: false, maxLength: 50));
AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: , unicode: false));
//DropColumn("dbo.Students", "StudentName");
RenameColumn("dbo.Students", "StudentName", "Name");
} public override void Down()
{
//AddColumn("dbo.Students", "StudentName", c => c.String(nullable: false, maxLength: 50));
DropColumn("dbo.Students", "GradeTest");
//DropColumn("dbo.Students", "Name");
RenameColumn("dbo.Students", "Name", "StudentName");
}
}
}

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

Entity Framework(六):数据迁移的更多相关文章

  1. Entity Framework CodeFirst数据迁移

    前言 紧接着前面一篇博文Entity Framework CodeFirst尝试. 我们知道无论是“Database First”还是“Model First”当模型发生改变了都可以通过Visual ...

  2. Entity Framework Migrations 数据迁移

    在使用Entity Framework 过程中,经常会遇到需要变更model 的状况,此时可以使用Migrations ,将每次变更记录以便后续更换机器或是运行在生产环境,持久层可保持一致. 在Pac ...

  3. Entity Framework Code First 迁移

    Entity Framework CodeFirst数据迁移 http://www.cnblogs.com/aehyok/p/3325459.html Entity Framework Code Fi ...

  4. Entity Framework 插入数据 解决主键非自增问题

    http://blog.csdn.net/educast/article/details/8632806 与Entity Framework相伴的日子痛并快乐着.今天和大家分享一下一个快乐,两个痛苦. ...

  5. ASP.NET Core 入门教程 8、ASP.NET Core + Entity Framework Core 数据访问入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC 集成 EF Core 介绍&操作步骤 ASP.NET Core MVC 使用 EF Core + Linq to Entity ...

  6. 论 微服务 和 Entity Framework 对 数据 的 割裂

    微服务 的 本质 是 面向对象, 微服务 是 面向对象 对 数据中心 发起的挑战, 在 微服务 架构下, “数据为中心” 的 传统架构 被 严重 割裂, 微服务 的 先天矛盾, 是 对象 和 数据 的 ...

  7. ASP.NET Core 入门笔记9,ASP.NET Core + Entity Framework Core 数据访问入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC 集成 EF Core 介绍&操作步骤 ASP.NET Core MVC 使用 EF Core + Linq to Entity ...

  8. Entity Framework CodeFirst------数据迁移(二)

    众所周知当我们的项目涉及到数据库时,随着需求或大或小的 变更后,我们之前设计好的数据模型会发生部分的更改,导致数据表.或者数据字段的增加.修改等,这个时候我们就需要对数据库结构进行修改,如果我们之前采 ...

  9. Entity Framework 丢失数据链接的绑定,在已绑好的EDMX中提示“Choose Your Data Connection”

    早先做的一个练手的项目中, 使用到了Entity framework . 最近碰到一个问题,在edmx 里面选择“Update model from Database” 的时候提示了 “Choose ...

  10. ASP.NET MVC+Entity Framework code first 迁移

    再来一张,选择 MVC 模版,其他的没选过,不会用 =_=!! 身份验证用个人用户账户,这个是为了偷懒,话说 ASP.NET Identity  还是很给力的,不用白不用 ^_^~ 点击确定之后,会看 ...

随机推荐

  1. Oracle中CBO优化器简介

    Oracle中CBO优化器简介 Oracle数据库中的优化器是SQL分析和执行的优化工具.它负责制定SQL的执行计划,也就是它负责保证SQL的执行计划的效率最高,比如优化器决定Oracle以什么样的方 ...

  2. 算法笔记_016:凸包问题(Java)

    目录 1 问题描述 2 解决方案 2.1 蛮力法 1 问题描述 给定一个平面上n个点的集合,它的凸包就是包含所有这些点的最小凸多边形,求取满足此条件的所有点. 另外,形象生动的描述: (1)我们可以把 ...

  3. 【Linux】cat命令

    用途 cat用于将一个档案的内容连续的打印在屏幕上 全称 cat的全称是Conctaenate 参数 -A :相当于-vTE的整合选项,可列出一些特殊字符而不是空白而已 -b :列出行号,仅针对非空白 ...

  4. C# 实体集合和实体转换成相应的string、XDocument、XElement、XDocument

    https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx XElement.Parse 方法 ( ...

  5. 中文latex参考文献格式

    中文latex参考文献格式 原来英文: \begin{thebibliography}{1} \bibitem{Ben-Shimon2015RecSys} D.~Ben-Shimon, A.~Tsik ...

  6. Php开发工具:PhpStorm=webstorm+php+db/SQL

    下载地址:https://www.jetbrains.com/zh/phpstorm/specials/phpstorm/phpstorm.html?utm_source=baidu&utm_ ...

  7. FFmpeg音视频同步示例

    原文地址:https://my.oschina.net/u/555002/blog/79324 前面整个的一段时间,我们有了一个几乎无用的电影播放器.当然,它能播放视频,也能播放音频,但是它还不能被称 ...

  8. map 类简介和例程

    一.标准库的map类型 使用map得包含map类所在的头文件 template < class Key, class Type, class Traits = less<Key>, ...

  9. 日期常用操作类DateUtil

    一.给定yyyy-MM-dd hh:mm:ss格式的字符串,返回Date. public Date convertStr2Date(String dateString) { try { SimpleD ...

  10. 【实用代码片段】将json数据绑定到html元素 (转)

    jQuery扩展 jQuery.fn.extend({ 'jsonBind':function(json){ var dom=this; dom.find('[json-bind]').each(fu ...