Entity Framework(六):数据迁移
在前面的几篇文章中,简单的介绍了如何使用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(六):数据迁移的更多相关文章
- Entity Framework CodeFirst数据迁移
前言 紧接着前面一篇博文Entity Framework CodeFirst尝试. 我们知道无论是“Database First”还是“Model First”当模型发生改变了都可以通过Visual ...
- Entity Framework Migrations 数据迁移
在使用Entity Framework 过程中,经常会遇到需要变更model 的状况,此时可以使用Migrations ,将每次变更记录以便后续更换机器或是运行在生产环境,持久层可保持一致. 在Pac ...
- Entity Framework Code First 迁移
Entity Framework CodeFirst数据迁移 http://www.cnblogs.com/aehyok/p/3325459.html Entity Framework Code Fi ...
- Entity Framework 插入数据 解决主键非自增问题
http://blog.csdn.net/educast/article/details/8632806 与Entity Framework相伴的日子痛并快乐着.今天和大家分享一下一个快乐,两个痛苦. ...
- 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 ...
- 论 微服务 和 Entity Framework 对 数据 的 割裂
微服务 的 本质 是 面向对象, 微服务 是 面向对象 对 数据中心 发起的挑战, 在 微服务 架构下, “数据为中心” 的 传统架构 被 严重 割裂, 微服务 的 先天矛盾, 是 对象 和 数据 的 ...
- 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 ...
- Entity Framework CodeFirst------数据迁移(二)
众所周知当我们的项目涉及到数据库时,随着需求或大或小的 变更后,我们之前设计好的数据模型会发生部分的更改,导致数据表.或者数据字段的增加.修改等,这个时候我们就需要对数据库结构进行修改,如果我们之前采 ...
- Entity Framework 丢失数据链接的绑定,在已绑好的EDMX中提示“Choose Your Data Connection”
早先做的一个练手的项目中, 使用到了Entity framework . 最近碰到一个问题,在edmx 里面选择“Update model from Database” 的时候提示了 “Choose ...
- ASP.NET MVC+Entity Framework code first 迁移
再来一张,选择 MVC 模版,其他的没选过,不会用 =_=!! 身份验证用个人用户账户,这个是为了偷懒,话说 ASP.NET Identity 还是很给力的,不用白不用 ^_^~ 点击确定之后,会看 ...
随机推荐
- Oracle中CBO优化器简介
Oracle中CBO优化器简介 Oracle数据库中的优化器是SQL分析和执行的优化工具.它负责制定SQL的执行计划,也就是它负责保证SQL的执行计划的效率最高,比如优化器决定Oracle以什么样的方 ...
- 算法笔记_016:凸包问题(Java)
目录 1 问题描述 2 解决方案 2.1 蛮力法 1 问题描述 给定一个平面上n个点的集合,它的凸包就是包含所有这些点的最小凸多边形,求取满足此条件的所有点. 另外,形象生动的描述: (1)我们可以把 ...
- 【Linux】cat命令
用途 cat用于将一个档案的内容连续的打印在屏幕上 全称 cat的全称是Conctaenate 参数 -A :相当于-vTE的整合选项,可列出一些特殊字符而不是空白而已 -b :列出行号,仅针对非空白 ...
- C# 实体集合和实体转换成相应的string、XDocument、XElement、XDocument
https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx XElement.Parse 方法 ( ...
- 中文latex参考文献格式
中文latex参考文献格式 原来英文: \begin{thebibliography}{1} \bibitem{Ben-Shimon2015RecSys} D.~Ben-Shimon, A.~Tsik ...
- Php开发工具:PhpStorm=webstorm+php+db/SQL
下载地址:https://www.jetbrains.com/zh/phpstorm/specials/phpstorm/phpstorm.html?utm_source=baidu&utm_ ...
- FFmpeg音视频同步示例
原文地址:https://my.oschina.net/u/555002/blog/79324 前面整个的一段时间,我们有了一个几乎无用的电影播放器.当然,它能播放视频,也能播放音频,但是它还不能被称 ...
- map 类简介和例程
一.标准库的map类型 使用map得包含map类所在的头文件 template < class Key, class Type, class Traits = less<Key>, ...
- 日期常用操作类DateUtil
一.给定yyyy-MM-dd hh:mm:ss格式的字符串,返回Date. public Date convertStr2Date(String dateString) { try { SimpleD ...
- 【实用代码片段】将json数据绑定到html元素 (转)
jQuery扩展 jQuery.fn.extend({ 'jsonBind':function(json){ var dom=this; dom.find('[json-bind]').each(fu ...