zoukankan      html  css  js  c++  java
  • Code First 数据库迁移

    当 Entity Framework Code First 的数据模型发生改变时,默认会引发一个System.InvalidOperationException 的异常。解决方法是使用DropCreateDatabaseAlways 或DropCreateDatabaseIfModelChanges,让Entity Framework 自动将数据库删除,然后重新创建。不过,这种方式过于残暴,应该使用更人性化的方式,让Entity Framework 帮助我们自动调整数据库架构。并且仍然保留现有数据库中的数据。而这种开发技术就是 Code First 数据库迁移(DB Migration)。

    首先,我们先用 Code First 方式建立一个简单的ASP.NET MVC4 应用程序

    在Models 文件夹下建立两个实体类Member、Guestbook。

    Member 实体类定义如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models  
    2. {  
    3.     public partial class Member  
    4.     {  
    5.         public Member()  
    6.         {  
    7.             this.Guestbooks = new List<Guestbook>();  
    8.         }  
    9.   
    10.         public int Id { get; set; }  
    11.         public string Name { get; set; }  
    12.         public string Email { get; set; }  
    13.         public virtual ICollection<Guestbook> Guestbooks { get; set; }  
    14.     }  
    15. }  

    Guestbook 实体类定义如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models  
    2. {  
    3.     public partial class Guestbook  
    4.     {  
    5.         public int Id { get; set; }  
    6.         public string Message { get; set; }  
    7.         public System.DateTime CreatedOn { get; set; }  
    8.         public int MemberId { get; set; }  
    9.         public virtual Member Member { get; set; }  
    10.     }  
    11. }  

    在Models 文件夹下建立Mapping 文件夹,并建立对应实体类的关系映射类MemberMap 、GuestbookMap

    MemberMap 类定义如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models.Mapping  
    2. {  
    3.     public class MemberMap : EntityTypeConfiguration<Member>  
    4.     {  
    5.         public MemberMap()  
    6.         {  
    7.             // Primary Key  
    8.             this.HasKey(t => t.Id);  
    9.   
    10.             // Properties  
    11.             this.Property(t => t.Id)  
    12.                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);  
    13.   
    14.             this.Property(t => t.Name)  
    15.                 .IsRequired()  
    16.                 .HasMaxLength(10);  
    17.   
    18.             this.Property(t => t.Email)  
    19.                 .IsRequired()  
    20.                 .HasMaxLength(200);  
    21.   
    22.             // Table & Column Mappings  
    23.             this.ToTable("Member");  
    24.             this.Property(t => t.Id).HasColumnName("Id");  
    25.             this.Property(t => t.Name).HasColumnName("Name");  
    26.             this.Property(t => t.Email).HasColumnName("Email");  
    27.         }  
    28.     }  
    29. }  

    GuestbookMap 类定义如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models.Mapping  
    2. {  
    3.     public class GuestbookMap : EntityTypeConfiguration<Guestbook>  
    4.     {  
    5.         public GuestbookMap()  
    6.         {  
    7.             // Primary Key  
    8.             this.HasKey(t => t.Id);  
    9.   
    10.             // Properties  
    11.             this.Property(t => t.Message)  
    12.                 .IsRequired()  
    13.                 .HasMaxLength(200);  
    14.   
    15.             // Table & Column Mappings  
    16.             this.ToTable("Guestbook");  
    17.             this.Property(t => t.Id).HasColumnName("Id");  
    18.             this.Property(t => t.Message).HasColumnName("Message");  
    19.             this.Property(t => t.CreatedOn).HasColumnName("CreatedOn");  
    20.             this.Property(t => t.MemberId).HasColumnName("MemberId");  
    21.   
    22.             // Relationships  
    23.             this.HasRequired(t => t.Member)  
    24.                 .WithMany(t => t.Guestbooks)  
    25.                 .HasForeignKey(d => d.MemberId);  
    26.   
    27.         }  
    28.     }  
    29. }  

    在Models 建立数据库上下文类CodeFirstDemoContext

    CodeFirstDemoContext 类定义如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models  
    2. {  
    3.     public partial class CodeFirstDemoContext : DbContext  
    4.     {  
    5.         static CodeFirstDemoContext()  
    6.         {  
    7.             //Database.SetInitializer<CodeFirstDemoContext>(new DropCreateDatabaseIfModelChanges<CodeFirstDemoContext>());  
    8.         }  
    9.   
    10.         public CodeFirstDemoContext()  
    11.             : base("Name=CodeFirstDemoContext")  
    12.         {  
    13.         }  
    14.   
    15.         public DbSet<Guestbook> Guestbooks { get; set; }  
    16.         public DbSet<Member> Members { get; set; }  
    17.   
    18.         protected override void OnModelCreating(DbModelBuilder modelBuilder)  
    19.         {  
    20.             modelBuilder.Configurations.Add(new GuestbookMap());  
    21.             modelBuilder.Configurations.Add(new MemberMap());  
    22.         }  
    23.     }  
    24. }  

    Models 文件夹结构下

    以上就是一个简单的 Code First 结构了

    接下来在Web.config 添加数据库连接字符串

    [csharp] view plain copy
    1. <connectionStrings>  
    2.     <add name="CodeFirstDemoContext" connectionString="Data Source=vin-pc;Initial Catalog=CodeFirstDemo;Persist Security Info=True;User ID=sa;Password=123456;MultipleActiveResultSets=True"  
    3.       providerName="System.Data.SqlClient" />  
    4.   </connectionStrings>  

    然后添加一个控制器HomeController

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Controllers  
    2. {  
    3.     public class HomeController : Controller  
    4.     {  
    5.         //  
    6.         // GET: /Home/  
    7.   
    8.         public ActionResult Index()  
    9.         {  
    10.             CodeFirstDemoContext db = new CodeFirstDemoContext();  
    11.             Member member = new Member { Name = "tt", Email = "qwe@qq.com" };  
    12.             db.Members.Add(member);  
    13.             db.SaveChanges();  
    14.   
    15.             return View();  
    16.         }  
    17.   
    18.     }  
    19. }  

    EF Code First 如何记录版本

    当应用程序通过EF Code First 创建数据库后,在此数据库中讲会自动创建一个名为 dbo. __MigrationHistory 的系统数据表,如下图所示:

    打开dbo. __MigrationHistory 会发现三个字段:MigrationId 字段用来记录这次由 EFCode First 所创建的一个表示名称,也可以称为一个版本代码;Model 字段表示这次创建时的模型数据,这是由 Entity Framework 将所有数据模型串行化后的版本,所以看不出是什么;ProductVersion 字段表示当前使用的Entity Framework 版本,如下图所示:

    如果尚未启用数据库迁移功能,每次在应用程序运行时,都会对比程序中当前的数据模型,与数据库中dbo. __MigrationHistory 表的Model 字段中的值是否一致,如果不一致,默认就会发生异常。

    如果启用数据库迁移功能之后,这个表就会开始记录每次数据模型变动的记录与版本。

    启用数据库迁移

    若要在项目中启用数据库迁移功能,必须先开启程序包管理器控制台(Package Manager Console)窗口,然后输入 Enable-Migrations指令,如下图:

    运行 Enable-Migrations 指令的过程中, Visual Studio 会在项目里创建一个Migrations 目录,该目录下还创建有两个文件,201309120825043_InitialCreate.cs 、Configuration.cs,如下图:

    1.      201309120825043_InitialCreate.cs

    在启用数据库迁移之前,由于已经通过 Code First 在数据库中创建好了相关的数据库结构,也创建了一个初始的dbo. __MigrationHistory 数据表,表中也有一条数据,这条数据的MigrationId值正好会等于文档名。VS会将dbo. __MigrationHistory 表的Model 值读出,并创建这个类的属性,其属性就是包含那次数据模型的完整描述。

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Migrations  
    2. {  
    3.     using System;  
    4.     using System.Data.Entity.Migrations;  
    5.       
    6.     public partial class InitialCreate : DbMigration  
    7.     {  
    8.         public override void Up()  
    9.         {  
    10.             CreateTable(  
    11.                 "dbo.Guestbook",  
    12.                 c => new  
    13.                     {  
    14.                         Id = c.Int(nullable: false, identity: true),  
    15.                         Message = c.String(nullable: false, maxLength: 200),  
    16.                         CreatedOn = c.DateTime(nullable: false),  
    17.                         MemberId = c.Int(nullable: false),  
    18.                     })  
    19.                 .PrimaryKey(t => t.Id)  
    20.                 .ForeignKey("dbo.Member", t => t.MemberId, cascadeDelete: true)  
    21.                 .Index(t => t.MemberId);  
    22.               
    23.             CreateTable(  
    24.                 "dbo.Member",  
    25.                 c => new  
    26.                     {  
    27.                         Id = c.Int(nullable: false, identity: true),  
    28.                         Name = c.String(nullable: false, maxLength: 5),  
    29.                         Email = c.String(nullable: false, maxLength: 200),  
    30.                     })  
    31.                 .PrimaryKey(t => t.Id);  
    32.               
    33.         }  
    34.           
    35.         public override void Down()  
    36.         {  
    37.             DropIndex("dbo.Guestbook", new[] { "MemberId" });  
    38.             DropForeignKey("dbo.Guestbook", "MemberId", "dbo.Member");  
    39.             DropTable("dbo.Member");  
    40.             DropTable("dbo.Guestbook");  
    41.         }  
    42.     }  
    43. }  

    2.      Configuration.cs

    这个类定义了运行数据库迁移时该有的行为。默认情况下,数据库并不会发生迁移动作,除非将 Configuration() 内的 AutomaticMigrationsEnabled 改为 true,才会让 CodeFirst 自动迁移数据库。

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Migrations  
    2. {  
    3.     using System;  
    4.     using System.Data.Entity;  
    5.     using System.Data.Entity.Migrations;  
    6.     using System.Linq;  
    7.   
    8.     internal sealed class Configuration : DbMigrationsConfiguration<CodeFirstDemo.Models.CodeFirstDemoContext>  
    9.     {  
    10.         public Configuration()  
    11.         {  
    12.             AutomaticMigrationsEnabled = false;  
    13.         }  
    14.   
    15.         protected override void Seed(CodeFirstDemo.Models.CodeFirstDemoContext context)  
    16.         {  
    17.             //  This method will be called after migrating to the latest version.  
    18.   
    19.             //  You can use the DbSet<T>.AddOrUpdate() helper extension method   
    20.             //  to avoid creating duplicate seed data. E.g.  
    21.             //  
    22.             //    context.People.AddOrUpdate(  
    23.             //      p => p.FullName,  
    24.             //      new Person { FullName = "Andrew Peters" },  
    25.             //      new Person { FullName = "Brice Lambson" },  
    26.             //      new Person { FullName = "Rowan Miller" }  
    27.             //    );  
    28.             //  
    29.         }  
    30.     }  
    31. }  

    运行数据库迁移

    下面来更改 Member 的数据模型,添加两个字段,分别是 UserName 和 Password 属性。

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models  
    2. {  
    3.     public partial class Member  
    4.     {  
    5.         public Member()  
    6.         {  
    7.             this.Guestbooks = new List<Guestbook>();  
    8.         }  
    9.   
    10.         public int Id { get; set; }  
    11.         public string Name { get; set; }  
    12.         public string Email { get; set; }  
    13.         public string UserName { get; set; }  
    14.         public string Password { get; set; }  
    15.         public virtual ICollection<Guestbook> Guestbooks { get; set; }  
    16.     }  
    17. }  

    通过Package Manager Console 输入 Add-Migration 指令,来新增一条数据库迁移版本,输入时必须带上一个“版本名称”参数。例如,想要取名为AddUsernamePassword,则可以输入以下指令:

    运行完成后,会在 Migrations 文件夹新增一个文件,如下图:

    这次运行 Add-Migration 指令,所代表的意思就是新增一次运行数据库迁移命令,VS2012会自动对比当前数据库中的 Model 定义与当前更改过的数据模型,并将差异的字段变化写入这个自动新增的类内,程序代码如下:

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Migrations  
    2. {  
    3.     using System;  
    4.     using System.Data.Entity.Migrations;  
    5.       
    6.     public partial class AddUsernamePassword : DbMigration  
    7.     {  
    8.         public override void Up()  
    9.         {  
    10.             AddColumn("dbo.Member", "UserName", c => c.String());  
    11.             AddColumn("dbo.Member", "Password", c => c.String());  
    12.         }  
    13.           
    14.         public override void Down()  
    15.         {  
    16.             DropColumn("dbo.Member", "Password");  
    17.             DropColumn("dbo.Member", "UserName");  
    18.         }  
    19.     }  
    20. }  

    NOTES

    每一次新增数据库迁移版本,其类内都会包含一个Up() 方法与 Down() 方法,所代表的意思分别是“升级数据库”与“降级数据库”的动作,所以,数据库迁移不仅仅只是将数据库升级,还可以恢复到旧版本。

    当前还没有对数据库做任何迁移动作,所以数据库中的数据结构并没有任何改变,现在,手动在 Member 数据表中输入几条数据,以确认待会儿数据库迁移(升级)之后数据是否消失,如图:

    接着,对数据库进行迁移动作,在程序包管理控制台(Package Manager Console)窗口中输入Update-Database指令,如图:

    更新数据库成功之后,可以查看 Member  数据表结构是否发生变化,以及数据表原来的数据是否存在:

    NOTES

    我们都知道,在客户端数据库通常是无法直接联机的,客户的生产环境通常也没有安装VS2012,那么如果数据库迁移动作要进行套用时,应该 怎么办呢?可以通过 Update-Database 指令的其他参数自动生产数据库迁移的 T-SQL 脚本,然后携带 T-SQL 脚本文件到正式主机部署或更新即可。

    Update-Database 指令的–SourceMigration 参数可以指定来源斑斑驳驳,-Targetigration 参数可以指定目标版本, -Script 参数可以用来输出 T-SQL 脚本。以下是生成本次数据库迁移(升级)的 T-SQL 指令演示:

    Update-Database –SourceMigration201309120825043_InitialCreate –TargetMigration 201309130055351_AddUsernamePassword-Script

    如果要生成数据库降级的 T-SQL,则不能使用–SourceMigration 参数,直接指定–TargetMigration 参数即可,演示如下:

    Update-Database –TargetMigration201309120825043_InitialCreate –Script

    如果要还原数据库带添加 Code First 之前的初始状态,可以输入以下指令:

    Update-Database  -TragetMigration:$InitialDatabase –Script

    自定义数据库迁移规则

    当了解数据库迁移的规则之后,如果希望在数据库迁移的过程中进行一些微调,例如, Entity Framework 并不支持自动设置字段的默认值,假设我们在 Member 数据模型中想添加一个新的 CreatedOn 属性表示会员的注册日期,并且希望在数据库中自动加上 getdate() 默认值,这时就必须要自定义数据库迁移的规则。

    首先更改 Member 数据模型,加上 CreatedOn 属性

    Member.cs

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models  
    2. {  
    3.     public partial class Member  
    4.     {  
    5.         public Member()  
    6.         {  
    7.             this.Guestbooks = new List<Guestbook>();  
    8.         }  
    9.   
    10.         public int Id { get; set; }  
    11.         public string Name { get; set; }  
    12.         public string Email { get; set; }  
    13.         public string UserName { get; set; }  
    14.         public string Password { get; set; }  
    15.         public DateTime CreatedOn { get; set; }  
    16.         public virtual ICollection<Guestbook> Guestbooks { get; set; }  
    17.     }  
    18. }  

    MemberMap.cs

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Models.Mapping  
    2. {  
    3.     public class MemberMap : EntityTypeConfiguration<Member>  
    4.     {  
    5.         public MemberMap()  
    6.         {  
    7.             // Primary Key  
    8.             this.HasKey(t => t.Id);  
    9.   
    10.             // Properties  
    11.             this.Property(t => t.Id)  
    12.                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);  
    13.   
    14.             this.Property(t => t.Name)  
    15.                 .IsRequired()  
    16.                 .HasMaxLength(10);  
    17.   
    18.             this.Property(t => t.Email)  
    19.                 .IsRequired()  
    20.                 .HasMaxLength(200);  
    21.   
    22.             this.Property(t => t.CreatedOn)  
    23.                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);  
    24.   
    25.             // Table & Column Mappings  
    26.             this.ToTable("Member");  
    27.             this.Property(t => t.Id).HasColumnName("Id");  
    28.             this.Property(t => t.Name).HasColumnName("Name");  
    29.             this.Property(t => t.Email).HasColumnName("Email");  
    30.         }  
    31.     }  
    32. }  

    然后运行一次 Add-Migration指令,并指定版本名称为 AddMemberCreatedOn

    这时,再 Migrations 目录下多出一个201309130144538_AddMemberCreatedOn.cs 文件

    [csharp] view plain copy
    1. namespace CodeFirstDemo.Migrations  
    2. {  
    3.     using System;  
    4.     using System.Data.Entity.Migrations;  
    5.       
    6.     public partial class AddMemberCreatedOn : DbMigration  
    7.     {  
    8.         public override void Up()  
    9.         {  
    10.             AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));  
    11.         }  
    12.           
    13.         public override void Down()  
    14.         {  
    15.             DropColumn("dbo.Member", "CreatedOn");  
    16.         }  
    17.     }  
    18. }  

    这次我们用不一样的参数来运行数据库迁移,加上–Script 参数,Update-Database –Script

    运行完后,会输出完整的数据库更新 T-SQL 脚本,其中第一行就是在 Member 数据表中新增一个 CreatedOn 字段,而且会看到该字段已经给予‘1900-01-01T00:00:00.000’ 这个默认值。第二行则是在 _MigrationHistory新增一条版本记录,如下图:

    此时,可以自定义201309130144538_AddMemberCreatedOn.cs 类里的 Up() 方法,在新增字段的地方改用Sql()方法,传入一段自定义的 T-SQL 脚本来创建字段,并改用自己的方法新增字段,如此一来,即可让数据库迁移在升级是自动加上此字段的默认值。

    [csharp] view plain copy
    1. public override void Up()  
    2.         {  
    3.             //AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));  
    4.             Sql("ALTER TABLE [dbo].[Member] ADD [CreatedOn] [datetime] NOT NULL DEFAULT getdate()");  
    5.         }  

    最后,运行 Update-Database 指令,这是再去检查 Member 数据表,可以看到,数据库迁移升级后的 CreatedOn 字段拥有了我们想要的 getdate() 默认值,如下图:

    TIPS

    在数据库迁移类中除了有 Up() 方法外,还有 Down() 方法,必须留意当降级时必要的架构的变更动作,如果自定义数据库迁移的规则写不好,可能会导致降级失败或数据库结构紊乱

    自动数据库迁移

    如果要启用自动数据库迁移的话,在Database.SetInitializer() 方法中使用System.Data.Entity.MigrateDatabaseToLatestVersion泛型类型,并且传入两个参数,第一个是 数据上下文类,第二个是在启用数据库迁移时自动生成的 Configuration 类,这个类喂鱼 Migrations 目录下,所以记得要加上命名空间:

    [csharp] view plain copy
    1. Database.SetInitializer(new MigrateDatabaseToLatestVersion<CodeFirstDemoContext, Migrations.Configuration>());  

    接着再开启MigrationsConfiguration.cs 设置AutomaticMigrationsEnbaled 属性为 ture 即可

    [csharp] view plain copy
    1. AutomaticMigrationsEnabled = true;  

    如此一来,日后所以的数据模型变动时,都会通过数据库迁移功能自动升级数据库,当每次自动升级发生时,也会在 dbo._MigrationHistory 系统数据表里记录,并以AutomaticMigration 命名,如下图:

    如何避免数据库被自动创建或自动迁移

    如果想要避免数据库被自动创建或自动迁移,则修改Database.SetInitializer() 方法,如:

    [csharp] view plain copy
    1. Database.SetInitializer<CodeFirstDemoContext>(null);  

    即可避免数据库被自动创建或自动迁移。

  • 相关阅读:
    Editor REST Client
    log配置
    spring-boot-configuration-processor
    http请求
    做项目用到的一些正则表达式,贴出来共享
    批量插入的实现
    sql执行顺序对比
    Java常用的并发工具类:CountDownLatch、CyclicBarrier、Semaphore、Exchanger
    spring中bean的生命周期
    多属性的对象列表的两种排序方法
  • 原文地址:https://www.cnblogs.com/dingfangbo/p/5771797.html
Copyright © 2011-2022 走看看