65.9K
CodeProject 正在变化。 阅读更多。
Home

Entity Framework 的 Code First 迁移

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (42投票s)

2014年7月28日

CPOL

7分钟阅读

viewsIcon

217701

downloadIcon

2862

本文将针对 Entity Framework 中的关系问题,提供以下问题的答案:是否创建数据库,如何向现有表中添加/删除字段,如何避免数据丢失,以及如何获取包含更改的数据库脚本。

引言

我之前的文章“使用 Code First 方法在 Entity Framework 中处理关系 (Fluent API)”介绍了实体关系,但您在阅读该文章时,可能会出现一些问题,例如:

  1. 每次都应该创建数据库吗?
  2. 如何向现有表中添加/删除字段?
  3. 当向现有表中添加或删除字段时,如何避免数据丢失?
  4. 可以获取包含数据库更改的脚本吗?

本文将为所有这些问题提供答案。

正如您在上一篇文章中看到的,您向项目中添加了一些实体类,并且可能添加或删除了实体中的字段。这就是为什么数据模型频繁更改的原因。Entity Framework 提供了几种创建数据库的配置选项,例如:如果数据库不存在则创建它,或者在每次更改实体或数据模型时自动删除并重新创建数据库。

假设您首先选择了“如果数据库不存在则创建”的配置选项,然后添加了一个新实体并运行了应用程序。您会收到一个错误,例如“数据库 'xx' 无法创建,因为它已存在”。但是,当您使用第二个配置选项时,添加或删除实体中的字段,更改实体类,或者在 DbContext 类中进行更改,下次运行应用程序时,它会自动删除现有数据库,创建一个与模型匹配的新数据库,并用测试数据填充它。

Entity Framework 的迁移功能使我们能够在不删除并重新创建数据库的情况下,更改实体或数据模型,并将这些更改部署到数据库服务器,从而更新数据库架构。

我们学习 MVC 与 Entity Framework 的路线图

从数据模型创建数据库

为了理解 Entity Framework Code First 方法中的迁移,我们创建一个实体并使用 Fluent API 定义它们的配置。我们将创建两个类库项目,一个类库项目 (EF.Core) 包含实体,另一个项目 (EF.Data) 包含这些实体的配置以及 DbContext

让我们定义一个非常简单的数据模型。我们只是在 EF.Core 项目中定义它。下面的 Student 类定义是 EF.Core 项目下的 Student.cs 文件。

using System;  
namespace EF.Core  
{  
   public class Student  
   {  
      public Int64 Id { get; set; }  
      public string Name { get; set; }  
      public int Age { get; set; }  
   }  
}

首先,我们将 Entity Framework 包安装到 EF.Data 项目中,以便我们可以使用它。

在 **工具** 菜单中,单击 **库包管理器**,然后单击 **程序包管理器控制台**,然后在其中选择默认项目 EF.Data ,这意味着始终选择要安装包的项目。

在 PM> 提示符下,输入以下命令:

PM> Install-Package EntityFramework -Version 5.0.0

Install Entity Framework

图 1.1:安装 Entity Framework

我们将 EF.Core 项目 DLL 的引用添加到 EF.Data 项目中,以便可以使用数据模型创建数据库表。之后,我们为上述数据模型定义配置,该配置将在创建数据库表时使用。配置定义了 EF.Data 项目下的 Mapping 文件夹中的另一个类库项目 EF.Data 。对于 Student 数据模型,我们在 EF.Data 项目下的 StudentMap.cs 文件中创建 StudentMap 配置类的定义。

using System.ComponentModel.DataAnnotations.Schema;  
using System.Data.Entity.ModelConfiguration;  
using EF.Core;  
  
namespace EF.Data.Mapping  
{  
  public class StudentMap : EntityTypeConfiguration<Student>  
    {  
      public StudentMap()  
      {  
          //key  
          HasKey(t => t.Id);  
  
          //property  
          Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);  
          Property(t => t.Name);  
          Property(t => t.Age);          
  
          //table  
          ToTable("Students");  
      }  
    }  
} 

现在,在 EF.Data 项目下的 App.config 文件中定义连接字符串,以便我们可以创建一个具有适当名称的数据库。连接字符串是:

  <connectionStrings>  
    <add name="DbConnectionString" connectionString="Data Source=sandeepss-PC;
    Initial Catalog=EFCodeFirst;User ID=sa; Password=****" providerName="System.Data.SqlClient" />  
</connectionStrings> 

现在我们创建一个继承 DbContext 类的上下文类 EFDbContext (在 EFDbContext.cs 中)。在此类中,我们重写 OnModelCreating() 方法。此方法在上下文类 (EFDbContext) 的模型已初始化时被调用,但在模型被锁定并用于初始化上下文之前。以下是上下文类的代码片段:

using System.Data.Entity;  
using EF.Data.Mapping;  
  
namespace EF.Data  
{  
    public class EFDbContext : DbContext  
    {  
        public EFDbContext()  
            : base("name=DbConnectionString")  
        {  
  
        }  
  
        protected override void OnModelCreating(DbModelBuilder modelBuilder)  
        {  
            modelBuilder.Configurations.Add(new StudentMap());  
        }  
    }  
} 

如您所知,EF Code First 方法遵循约定优于配置的原则,因此在构造函数中,我们只需传递与 App.Config 文件同名的连接字符串名称,它就会连接到该服务器。在 OnModelCreating() 方法中,我们将一个配置类对象添加到 DbModelBuilder

我们创建一个控制台应用程序 EF.Console 来创建数据库并向数据库表中插入数据。在 Program.cs 中实现 Main 方法,如下所示。此代码创建我们上下文的新实例,然后使用它来插入新的 Student

using System;  
using EF.Core;  
using EF.Data;  
  
namespace EF.Console  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            System.Console.Write("Enter your name : ");  
            string name = System.Console.ReadLine();              
            System.Console.Write("Enter your age : ");  
            int age = 0;  
            Int32.TryParse(System.Console.ReadLine(), out age);  
  
            using (EFDbContext context = new EFDbContext())  
            {  
                Student student = new Student { Name = name, Age = age };  
                context.Entry(student).State = System.Data.EntityState.Added;  
                context.SaveChanges();  
            }  
            System.Console.ReadLine();             
        }  
    }  
}  

现在,我们运行上述代码,结果是数据库已在 SQL Server 中创建,并将一行插入到 Student 表中。

Database Created

图 1.2:数据库已创建

现在,我们通过在此类中添加新字段 IsCurrent 来更新数据模型,因此 Student 类的更新将如下所示:

using System;  
  
namespace EF.Core  
{  
   public class Student  
    {  
       public Int64 Id { get; set; }  
       public string Name { get; set; }  
       public int Age { get; set; }  
       public bool IsCurrent { get; set; }  
    }  
} 

根据上述解释,我们还需要更新其配置类 StudentMap ,如下所示:

using System.ComponentModel.DataAnnotations.Schema;  
using System.Data.Entity.ModelConfiguration;  
using EF.Core;  
  
namespace EF.Data.Mapping  
{  
    public class StudentMap : EntityTypeConfiguration<Student>  
    {  
        public StudentMap()  
        {  
            //key  
            HasKey(t => t.Id);  
  
            //property  
            Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);  
            Property(t => t.Name);  
            Property(t => t.Age);  
            Property(t => t.IsCurrent);  
  
            //table  
            ToTable("Students");  
        }  
    }  
}  

之后,我们将更新 Program.cs Main 方法,以便我们可以插入一个值来指示该学生是否为当前学生。

using System;  
using EF.Core;  
using EF.Data;  
  
namespace EF.Console  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            System.Console.Write("Enter your name : ");  
            string name = System.Console.ReadLine();  
            System.Console.Write("Enter your age : ");  
            int age = 0;  
            Int32.TryParse(System.Console.ReadLine(), out age);  
            System.Console.Write("You are current student");  
            bool isCurrent = System.Console.ReadLine() == "Yes" ? true : false;  
  
            using (EFDbContext context = new EFDbContext())  
            {  
                Student student = new Student { Name = name, Age = age, IsCurrent = isCurrent };  
                context.Entry(student).State = System.Data.EntityState.Added;  
                context.SaveChanges();  
            }  
            System.Console.ReadLine();  
        }  
    }  
} 

现在,我们运行应用程序并遇到此错误:

Error to add a record to database

图 1.3:添加记录到数据库时出错

上述错误表明数据模型自创建数据库以来已被更改,这是正确的。我们有一个解决方案来解决此错误,即不重新初始化上下文,因此我们在上下文类中定义一个静态构造函数,并在其中将数据库初始化程序设置为 null 。让我们看一下上下文类的以下更新代码片段:

using System.Data.Entity;
using EF.Data.Mapping;

namespace EF.Data
{
    public class EFDbContext : DbContext
    {
        static EFDbContext()
        {
           Database.SetInitializer<EFDbContext>(null);
        }

        public EFDbContext()
            : base("name=DbConnectionString")
        {

        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new StudentMap());
        }
    }
} 

现在我们运行应用程序并遇到此错误。

 Database Update Exception

图 1.4 数据库更新异常

出现上述错误是因为我们的数据模型实体在代码中已更新,而相关的数据库表尚未更新。我们有两种解决方案可以解决此错误:删除数据库或使用迁移。第一种方法没有用,因为在这种情况下我们会丢失数据库中的所有数据,因此我们将在下一节中研究第二种解决方案。

Code First 迁移

Code First 迁移用于更新数据库。这里我们将看看如何在我们的应用程序中使用它。让我们一步一步地看看。

在 **工具** 菜单中,单击 **库包管理器**,然后单击 **程序包管理器控制台**,然后在其中选择默认项目 EF.Data。这意味着始终选择包含上下文类的项目来进行迁移。

在 PM> 提示符下,输入以下命令:

PM> enable-migrations

运行上述命令时,您将看到一个控制台窗口,如下所示:

Enable code first migration

图 1.5:启用 Code First 迁移

此命令在 EF.Data 项目中添加了一个新文件夹 Migrations ,该文件夹包含一个带有默认设置的配置文件。

现在,我们在 Configuration 类的构造函数中添加配置设置,一个用于允许迁移,另一个用于在迁移时无数据丢失。这些属性的 Configuration 类的摘录是:

AutomaticMigrationsEnabled = true;  
AutomaticMigrationDataLossAllowed = false; 

我们将 AutomaticMigrationEnabled 属性设置为 true;这意味着我们正在使用自动 Code First 迁移,另一个属性 AutomaticMigrationDataLossAllowed 设置为 false。这意味着在迁移过程中,不会丢失该表数据库迁移中的任何现有数据。整个 Configuration 类如下:

namespace EF.Data.Migrations  
{  
    using System;  
    using System.Data.Entity;  
    using System.Data.Entity.Migrations;  
    using System.Linq;  
  
    internal sealed class Configuration : DbMigrationsConfiguration<EF.Data.EFDbContext>  
    {  
        public Configuration()  
        {  
            AutomaticMigrationsEnabled = true;  
            AutomaticMigrationDataLossAllowed = false;  
        }  
  
        protected override void Seed(EF.Data.EFDbContext context)  
        {  
            //  This method will be called after migrating to the latest version.  
  
            //  You can use the DbSet<T>.AddOrUpdate() helper extension method   
            //  to avoid creating duplicate seed data. E.g.  
            //  
            //    context.People.AddOrUpdate(  
            //      p => p.FullName,  
            //      new Person { FullName = "Andrew Peters" },  
            //      new Person { FullName = "Brice Lambson" },  
            //      new Person { FullName = "Rowan Miller" }  
            //    );  
            //  
        }  
    }  
}  

seed 方法用于将默认值插入数据库表。

之后,我们将使用程序包管理器控制台更新数据库。要在 PM> 提示符下更新数据库,请输入以下命令:

PM> Update-Database -Verbose

-Verbose”标志指定在控制台中显示应用于目标数据库的 SQL 语句。您将在程序包管理器控制台中获得如图所示的结果。

 Update existing database

图 1.6 更新现有数据库

现在我们检查数据库并使用我们的控制台应用程序添加一个新记录。我们发现数据库中没有数据丢失,并且应用程序运行顺畅,没有抛出任何异常。您可以在数据库中编写以下查询并获得如图 1.7 所示的结果。

SELECT [Id],[Name],[Age],[IsCurrent]FROM [EFCodeFirst].[dbo].[Students]  

Retrieve data from student table

图 1.7:从 student 表检索数据。

结论

本文介绍了使用 Entity Framework 的 Code First 迁移。我希望这两篇文章能让您更清楚地了解如何在我们的应用程序中使用 Entity Framework。如果您有任何疑问,请在评论中发布,或直接通过 https://twitter.com/ss_shekhawat 联系。

© . All rights reserved.