我们知道无论是“Database First”还是“Model First”当模型发生改变了都可以通过Visual Studio设计视图进行更新,那么对于Code First如何更新已有的模型呢?今天我们简单介绍一下Entity Framework的数据迁移功能。
Entity Framework配置
在开始今天的话题之前先来看一下Entity Framework的配置,因为有很多朋友因为配置文件的问题造成“Migrations”命令执行失败。
在建立一个应用程序之后我们可以通过在项目上右键“Nuget Packages Manage”进行EF包安装或者直接在“Package Manager Console”中输入“Install-Package EntityFramework”命令进行Entity Framework包安装。这个过程不仅下载并引入了EF框架相关程序集还进行了包的配置和应用用程序配置。下面是这两个配置文件:
packages.config文件:
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="EntityFramework" version="5.0.0" targetFramework="net45" /> </packages>
App.config文件:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration>
packages.config内容比较简单,首先是EF自身版本,然后在安装过程中根据当前应用的.NET Framework版本配置了“targetFramework”,因为不同的.NET Framework版本对应的EF程序集不同,这在安装过程中会自动识别并配置。
App.config中自动添加了“entityFramework”配置节,在EF包安装过程中自动根据当前环境配置了“defaultConnectionFactory”, “defaultConnectionFactory”是EF默认的连接配置,只有在没有配置连接字符串时生效。在上一篇文章中我们提到如果不进行连接字符串配置EF会自动识别并创建数据库到“.SQLEXPRESS”或者“LocalDb”,事实上就是通过这里识别的,可以看出我机器上没有“.SQLEXPRESS”就自动使用了“LocalDb”,配置默认连接到“LocalDbConnectionFactory”。
在本例中我们需要将连接指向“.SQL2008”上,有两种方式建立自己的连接:一种是直接配置“defaultConnectionFactory”,将默认生成的“System.Data.Entity.Infrastructure.LocalDbConnectionFactory”改成“System.Data.Entity.Infrastructure.SqlConnectionFactory”,然后在参数中编写我们的连接字符串(注意默认生成的数据库名为“项目名称.数据库上下文名称”)。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="Data Source=.SQL2008; UID=sa;PWD=123; MultipleActiveResultSets=True" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration>
另一种是通过传统的 “connectionStrings” 配置,只要配置了连接字符串“defaultConnectionFactory”将不再生效,EF会自动使用该连接。在接下来的示例中我们将采用这种方式。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <connectionStrings> <add name="CodeFirstDb" connectionString="Data Source=.SQL2008;Database=CodeFirstDb;UID=sa;PWD=123;MultipleActiveResultSets=true" providerName="System.Data.SqlClient"></add> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration>
Code First数据库迁移
现在让我们在上一篇文章Entity Framework 5.0系列之EF概览中的“Code First”示例的基础上给Order添加一个”Employee”属性,然后运行,不出意外的话你将看到如下异常:
从异常信息我们可以看出,EF已经检测到模型发生了改变,建议我们使用”Code First Migrations”对模型进行更新。事实上EF就是依靠上一篇文章中提到的“dbo.__MigrationHistory”表对我们的模型进行判断的,因为这张表中存储了我们的模型结构(在Model列中),如果运行程序时你跟踪SQL Server会发现EF执行了如下SQL进行模型修改判断:
SELECT Count(*) FROM sys.databases WHERE [name]=N'CodeFirstDb' go SELECT TABLE_SCHEMA SchemaName, TABLE_NAME Name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' go SELECT Count(*) FROM sys.databases WHERE [name]=N'CodeFirstDb' go SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT COUNT(1) AS [A1] FROM [dbo].[__MigrationHistory] AS [Extent1] ) AS [GroupBy1] go SELECT TOP (1) [Project1].[C1] AS [C1], [Project1].[MigrationId] AS [MigrationId], [Project1].[Model] AS [Model] FROM ( SELECT [Extent1].[MigrationId] AS [MigrationId], [Extent1].[Model] AS [Model], 1 AS [C1] FROM [dbo].[__MigrationHistory] AS [Extent1] ) AS [Project1] ORDER BY [Project1].[MigrationId] DESC go
在开始Code First数据库迁移之前,我们先对上一节编写的OrderContext类进行修改添加默认构造函数,因为Code First Migrations将会使用数据库上下文的默认构造函数进行数据迁移操作(尽管没有默认构造函数所有的数据操作都能正常进行,但是对于数据迁移这是必须的),因此我们需要添加一个默认构造函数,并且该构造函数中必须传入我们的数据库连接名称(如果使用上面我们说的第一种数据库连接配置方式则可以不用传递该参数),否则将会把更新应用到EF默认数据库上。下面是我们的OrderContext:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; namespace CodeFirst { public class OrderContext:DbContext { public OrderContext() : base("CodeFirstDb") { } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } } }
下面我们将借助于”Code First Magrations” 进行模型更新。
在Visual Studio中执行:Tools->Library Package Manager->Package Manager Console来打开包管理命令行:
在命令行中输入“Enable-Migrations -StartUpProjectName CodeFirst”然后回车(“CodeFirst”是你的项目名称,如果在“Package Manager Console”中选择了默认项目可以不设置“-StartUpProjectName”参数;如果多次执行此命令可以添加-Force参数):
注意:如果在运行“Enable-Migrations -StartUpProjectName CodeFirst”命令过程中收到如下错误,请尝试下面给出的方法: Enable-Migrations : The term 'Enable-Migrations' is not recognized as the name of a cmdlet, function, script file, or operable 1. 在Package Manager Console中执行命令:Import-Module F:[7]CSharpEntityFrameworkCodeFirstpackagesEntityFramework.5.0.0 oolsEntityFramework.PS3.psd1(后面的路径按照你EF包的实际位置修改,这个问题是由于没有导入相应命令造成的) 2. 执行“Install-Package EntityFramework -IncludePrerelease”命令重新安装最新版EF(也可以通过-Version参数指定具体版本)。 3. 更新Nuget,具体更新步骤见Installing NuGet。 4. 另外,命令执行过程中其他错误可以按照错误提示解决。 |
如果没有错误你的项目中将自动生成一个名为”Migrations“的文件夹,里面包含两个文件: Configuration.cs和201308211510117_InitialCreate.cs(201308211510117是时间戳)。
Configuration.cs:是迁移配置代码,一般我们不需要修改。
namespace CodeFirst.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration : DbMigrationsConfiguration<CodeFirst.OrderContext> { public Configuration() { AutomaticMigrationsEnabled = true; } protected override void Seed(CodeFirst.OrderContext 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" } // ); // } } }
201308211510117_InitialCreate.cs:以代码的形式记录了本地数据库的表结构定义。
namespace CodeFirst.Migrations { using System; using System.Data.Entity.Migrations; public partial class InitialCreate : DbMigration { public override void Up() { CreateTable( "dbo.Orders", c => new { Id = c.Int(nullable: false, identity: true), Customer = c.String(), OrderDate = c.DateTime(nullable: false), }) .PrimaryKey(t => t.Id); CreateTable( "dbo.OrderDetails", c => new { Id = c.Int(nullable: false, identity: true), Product = c.String(), UnitPrice = c.String(), OrderId = c.Int(nullable: false), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Orders", t => t.OrderId, cascadeDelete: true) .Index(t => t.OrderId); } public override void Down() { DropIndex("dbo.OrderDetails", new[] { "OrderId" }); DropForeignKey("dbo.OrderDetails", "OrderId", "dbo.Orders"); DropTable("dbo.OrderDetails"); DropTable("dbo.Orders"); } } }
现在在” Package Manager Console”中执行“Add-Migration AddEmployee”命令,该命令会自动比较模型和当前数据库中的表结构,然后执行“AddEmployee”添加一个”Employee”列,此时在”Migrations“文件夹会生成一个名为“201308240757094_AddEmployee.cs”的类(201308240757094是时间戳):
201308240757094_AddEmployee.cs内容如下:
namespace CodeFirst.Migrations { using System; using System.Data.Entity.Migrations; public partial class AddEmployee : DbMigration { public override void Up() { AddColumn("dbo.Orders", "Employee", c => c.String()); } public override void Down() { DropColumn("dbo.Orders", "Employee"); } } }
从这个类中我们可以看出事实上是给“Order”添加了“Employee”列,当然我们也可以手动修改这个类。
接下来,在“Package Manager Console”中执行“Update-Database -StartUpProjectName CodeFirst –Verbose”命令(添加-Verbose参数可以查看到更新脚本),进行数据库结构更新:
此时查看数据库,发现“Order”表已经多了一列:
OK,今天我们的内容就先到此,关于如何根据数据库生成代码类及其更多内容我们在后面的文章中再一起探讨。