写在前面:html
EF 中 Code First 的数据迁移网上有不少资料,我这份并没什么特别。Code First 建立视图网上也有不少资料,但好像很麻烦,并且亲测好像是无效的方法(多是我太笨,没搞成功),我摸索出了一种简单有效的方法,这里分享给你们。web
EF是Entity Framework(实体框架)的简写,是微软出品的用来操做数据库的一个框架,会ASP.NET MVC的朋友对他确定都不陌生。因为学艺不精,我对EF存在一疑虑,就不以【提问】的方式来问了,我以【总结】的方式来表达,若是总结有误的地方,还请看到的大神能够指正,并赐教我正确的认知,万分感谢。数据库
EF有三种使用方式:服务器
1) Db First 数据库优先网络
2) Model First 模型优先app
3) Code First 代码优先框架
上图中,前三种分别是DbFirst、ModelFirst和CodeFirst,而第4种也是CodeFirst。ide
1、DbFirst、ModelFirst必须从app.config/web.config中读取链接字符串,简直无情函数
不知道是我功底太差,仍是微软真的就是这么设计的,我发现一个问题:DbFirst和ModelFirst这两种模式,竟然只能从App.config/Web.config中读取数据库链接字符串,这是一个致命的问题,这意味着链接字符串不能被加密必须暴露给客户看(若是你是窗体应用程序的话)。(感谢 @lcs-帅 指正这一点) 这意味着咱们必需要 这样(利用ASP.NET加密和解密Web.config中链接字符串) 来对咱们的链接字符串进行加密,好像有点太复杂了。学习
若是不利用上面红色大字中提到的方法,那咱们大概能够有两种方式读取数据库链接字符串,第一种是在App.config/Web.config中读取:
1 <connectionStrings> 2 <add name="Model1" connectionString="data source=(LocalDb)\v11.0;initial catalog=数据库名;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="..略.." /> 3 </connectionStrings>
这种方式不太好,由于若是是窗体应用程序的话,等于就用明文告诉了别人你的数据库密码了。固然,你也能够对这一段字符串进行加密,就像这样:
1 <connectionStrings> 2 <add name="Model1" connectionString="ZGF0YSUyMHNvdXJjZSUzRCUyOExvY2FsRGIlMjklNUN2MTEuMCUzQmluaXRpYWwlMjBjYXRhbG9nJTNEJXU2NTcwJXU2MzZFJXU1RTkzJXU1NDBEJTNCaW50ZWdyYXRlZCUyMHNlY3VyaXR5JTNEVHJ1ZSUzQk11bHRpcGxlQWN0aXZlUmVzdWx0U2V0cyUzRFRydWUlM0JBcHAlM0RFbnRpdHlGcmFtZXdvcms=" providerName="..略.." /> 3 </connectionStrings>
而当你像上面这样对数据库链接字符串进行加密,此时你就等同于选择了使用第二种方式读取链接字符串了,第二种读读取数据库链接字符串的方式是:
1 string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["Model1"].ConnectionString; //可选 2 connStr = 解密(connStr); //可选 3 Model1 db = new Model1(connStr); //第二种设置数据库链接字符串的方式
这第二种方式的前2句代码是可选的,也就是说你不必定要从Web.config/App.config中读取数据库链接字符串,你也不必定要对数据库链接字符串进行加解密。
这第二种方式最重要的是第3句代码,也就是说你可使用一个字符串变量来作为数据库链接字符串,这意味着你不必定要从Web.config/App.config中读取数据库链接字符串。
然而!惋惜的是:在DbFirst和ModelFirst中,并不支持第二种设置数据库链接字符串的方式,这也就意味着DbFirst和ModelFirst的应用场景大打折扣!
在DbFirst或ModelFirst中,调用如下代码会报错,由于只有一个无参构造方法:
也许你(曾经我)自作聪明的想到一些办法,写一个【部分类】为 DbContext 添加一个有参构造函数:
事实证实是自作聪明,当你去操做任何一张表的时候,就报这个错误:
当你(我)看到这一个错误提示的时候,终于认命了:在DbFirst和ModelFirst中,数据库连按字符串只能写在App.config/Web.config中,简直无情。
2、CodeFirst 能够从代码中读取数据库链接字符串,没必要从Web.config/App.config中读取。
要在CodeFirst中对链接字符串进行加密,首先要添加一个带参构造函数,就像这样:
1 public Model1() 2 : base("name=Model11") 3 { 4 } 5 public Model1(string nameOrConnectionString) 6 : base(nameOrConnectionString) 7 { 8 }
而后就这样:
1 string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["Model1"].ConnectionString; //可选 2 connStr = 解密(connStr); //可选 3 Model1 db = new Model1(connStr); //第二种设置数据库链接字符串的方式
从配置文件中读取出加了密的链接字符串,再进行解密,最后应用于 DbContext 之中。
在CodeFirst中进行这样的操做程序不会报错,简直有爱。
3、【空 Code First 模型】与【来自数据库的 Code First 】异曲同工,是同样的
关于Code First,有两个能够选择,下图中的最后两个:
这二者是异曲同工,是同样的。
【空CodeFirst模型】就是新建一个空的模型,而后本身写实体类代码,根据这些实体类代码来生成数据库,生成数据库了以后,就与【来自数据库的CodeFirst】是同样的。
【来自数据库的CodeFirst】就是根据从数据库中选择的一些表/视图来生成实体类代码,这些自动生成的代码和你本身手写是同样同样的,也就是与【空CodeFirst模型+手写实体类代码】是同样的。
然而,并不彻底是!慢慢聊吧。
4、数据迁移
数据迁移,就是实体模型类发生变化以后(好比多一个字段,字段类型改变,或者多了一个实体类),数据库的表结构也跟着一块儿变化的过程。
4.1 为何要使用数据迁移
由于模型和实际的数据库结构可能会不同,不同的话程序运行就会有问题,因此须要使用数据迁移这个功能来帮助咱们解决问题。
用一个例子来讲明,建立控制台应用程序,建立【空CodeFirst模型】:
1 //数据库Model1 2 public class Model1 : DbContext 3 { 4 public Model1() : base("name=Model1") { } 5 public virtual DbSet<MyEntity> MyEntities { get; set; } 6 } 7 //实体类 (对应表:MyEntities) 8 public class MyEntity 9 { 10 public int Id { get; set; } 11 public string Name { get; set; } 12 }
修改数据库链接字符串,改为一个咱们熟悉的数据库服务器名,以避免数据库新建了以后,找不到在哪里:
<add name="Model1" connectionString="server=服务器名;uid=用户名;pwd=密码;database=新的数据库名;" providerName="System.Data.SqlClient" />
而后在Main方法中随便访问一下这个实体类:
1 using (var db = new Model1()) 2 { 3 //随便访问一下 MyEntities 表,以便让 EF 自动建立此表 4 var x = db.MyEntities.Find(1); 5 }
运行程序,就会获得一个新的数据库,新数据库里面有一个新表 MyEntities。(还有另外一个叫 __MigrationHistory 的表,是用于记录代码迁移的历史记录,也是代码迁移模式的开关,后面慢慢聊)
到这里为止,一切都正常。
如今,咱们再添加一个年龄Age字段:
1 //实体类 (对应表:MyEntities) 2 public class MyEntity 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 //添加了年龄字段 7 public int? Age { get; set; } 8 }
这时,再运行程序,就会报一个错误:
错误的大概意思就是:模型与实际数据库结构不一致,须要使用 Code First 中的数据迁移功能来解决问题。
4.2 数据迁移的用法
当模型与实际数据库结构不一致时,就须要使用数据迁移。数据迁移咱们只须要掌握三个方法就能够了,它们分别是:
1) 在当前项目中启用数据迁移 Enable-Migrations
2) 添加迁移版本 Add-Migration 版本名称
3) 更新数据库 Update-Database
首先第一步,须要在当前项目中启用数据迁移,打开【视图->其它窗口->程序包管理器控制台】快捷键是 Alt+V,E,O,而后输入命令:
Enable-Migrations
这样就能够在当前项目中启用数据迁移了,其效果是项目中会多出 Migrations 文件夹以及一些代码:
Migrations 这个文件夹就是数据迁移的相关文件夹,作为初学者,不要乱动里面的代码。
因为咱们的模型已经发生了改变,因此须要添加一个新的迁移版本,在程序包管理控制台执行以下命令:
Add-Migration v1
其中的v1表示版本号,本身随便取一个名字就能够了。此时,项目中多出一个文件:
这个文件的代码是:
1 //版本 v1 2 public partial class v1 : DbMigration 3 { 4 //版本升级 5 public override void Up() 6 { 7 //向 MyEntities 表添加 Age 字段 8 AddColumn("dbo.MyEntities", "Age", c => c.Int()); 9 } 10 11 //版本降级 12 public override void Down() 13 { 14 //向 MyEntities 表删除 Age 字段 15 DropColumn("dbo.MyEntities", "Age"); 16 } 17 }
代码中的注释是我写上去的,大概的为你们解释了一下是什么意思。到这里为止,数据库依然没有发生变化。
想要数据库发生变化,多出一个 Age 字段,那就要用到第三个命令:
Update-Database
执行成功以后,数据库就多出一个 Age 字段了。
此时,再运行程序,程序就不会报错了。由于此时的模型与实际数据库结构已经一致。
至此,例子已演示完毕。
这里有一个很是很是很是重要的细节,要注意:
若是你使用了 new Model1(connStr) 自定义数据库链接字符串,在使用数据迁移的命令时,所访问的数据库是你 web.config/app.config 中配置的数据库,并非你自定义链接的那个数据库。
这是一个巨坑,必定要注意:
使用了自定义数据库链接字符串时,虽然 web.config/app.config 中的明文数据库链接字符串并不必定要被发布,但在开发时也必定要在里面填写上正确的数据库链接信息。
5、关闭数据迁移
数据迁移的开启与关闭分两个层面来讲:
1) Visual Studio 项目是否开启了数据迁移
2) SQL Server 数据库是否开启了数据迁移
对于项目中是否开启了数据迁移,识别方法是:有没有 Migrations 这个文件夹以及里面的类文件。若是有就表示开启了,没有就没开启。
对于数据库中是否开启了数据迁移,识别方法是:有没有 __MIgrationHistory 这个表。若是有就表示开启了,没有就没开启。
如下讨论的数据迁移开启和关闭,都是指数据库中数据迁移的开启和关闭。
实际上 Code First 又有两种分支用法:
1) 数据库中 启用了数据迁移的 Code First
2) 数据库中 关闭了数据迁移的 Code First
这个开关并不在于你的项目中有没有 Migrations 这个文件夹,而在于:你的数据库里面,有没有 __MigrationHistory 这个表。
当你使用【空 Code First 模型】建立出来的数据库,默认就是开启了数据迁移的,关闭它的方法天然就是删除 __MigrationHistory 这个表。
而你使用【来自数据库的 Code First】默认这个数据库就是关闭了数据迁移的 ,用一个例子来讲明一下吧。
示例:
先确保你的数据库实例中有一个数据库,这个库里面有一些表,好比 Student 表,而且这个库没有 __MigrationHistory 这个表。
而后新建控制台程序,并建立【来自数据库的 Code First】,选择 student 表,自动产生模型代码和上下文类的代码:
1 //这是自动产生的代码 (我进行了删减) 2 public partial class Model1 : DbContext 3 { 4 public Model1(): base("name=Model1") { } 5 public virtual DbSet<student> student { get; set; } 6 }
1 //这是自动产生的代码 (我进行了删减) 2 public partial class student 3 { 4 public int id { get; set; } 5 public string name { get; set; } 6 }
而后在 Main 方法里随便调用一下,看看有没有问题:
1 using (var db = new Model1()) 2 { 3 //随便调用一下,看看有没有问题 4 var x = db.student.Find(1); 5 }
由于此时的模型与实际数据库结构是同样的,运行结果固然不会有问题了。
如今,咱们修改一下模型,在学生实体类中添加一个身份证的字段:
1 public partial class student 2 { 3 public int id { get; set; } 4 public string name { get; set; } 5 //添加身份证 idcard 字段 6 public string idcard { get; set; } 7 }
此时,咱们再运行程序,程序报错了,但报的错误并非让咱们进行数据迁移:
报的错误是:列名 'idcard' 无效。
这就是数据库中关闭了数据迁移的 Code First 的行为表现,不会提示你(要求你)进行数据迁移。
没有了数据迁移,咱们怎样才能让模型和实际数据库表结构同样呢?
很简单,你本身手动在数据库 student 表中添加一个 idcard 字段不就能够了吗。
确实,当咱们手动添加了 idcard 字段以后,再运行程序,程序就不报错了。
小总结:
1) 使用【空 Code First 模型】建立出来的数据库默认是开启数据迁移的,咱们只须要删除 __MigrationHistory 表就能够关闭数据迁移。
2) 使用【来自数据库的 Code First】这种方式,数据库默认就是关闭了数据迁移的。
3) 在数据库中关闭数据迁移的优势是:
你可能并不熟悉数据迁移是个什么鬼,那不如就让它见鬼去吧。关闭它,会让你进入你熟悉世界,经过手动改写实体类,手动改数据库表结构,来同步模型与实际表结构,挺好!
6、在关闭数据迁移的模式下,在 EF 实体模型中建立视图
数据库关闭了数据迁移的状况下,要在 EF 实体模型中建立视图,实在是太简单了。
你就编写一个实体类来当成视图,好比 v_student,你就把它当成是一张表同样来编写:
1 //我想写一个视图,先别急,先当它是一张表 2 public partial class v_student 3 { 4 public int id { get; set; } 5 public string name { get; set; } 6 //姓 7 public string first_name { get; set; } 8 }
1 public partial class Model1 : DbContext 2 { 3 public Model1(): base("name=Model1") { } 4 public virtual DbSet<student> student { get; set; } 5 //我想写一个视图,先别急,先当它是一张表 6 public virtual DbSet<v_student> v_student { get; set; } 7 }
而后,在 Main 方法中访问这个视图,看看有没有问题:
1 using (var db = new Model1()) 2 { 3 //访问一下这个视图,看看有没有问题 4 var x = db.v_student.Find(1); 5 }
运行。固然会有问题啦,会报这个错误:
注意到了吗,它只是说 v_student 无效,并无说让咱们作什么数据迁移。
好吧,难道咱们要傻傻的去建立一个 v_student 表吗? 难道不能够建立一个 v_student 视图?
固然能够,咱们来建立一个视图吧,在数据库中执行如下SQL语句:
1 create view v_student 2 as 3 select id,name,substring(name,1,1) first_name from student 4 go
而后,测试运行程序,程序不报错了。任务完成。
7、在开启据迁移的模式下,在 EF 实体模型中建立视图
也许你比较欣赏在数据库中开启了数据迁移的这种模式,在数据库中开启了数据迁移的模式下,能不能建立视图呢?若是能够,又如何建立视图呢?
网络上你能够搜索到不少 Code First 中建立视图的方法,这些方法都是指数据库中开启了数据迁移的 Code First 下的方法,但好像都挺麻烦的,反正我照着作并无作出来。
而后我摸索了一下,找出一种简单又实用的方法,这里分享给你们。没错,这一段落才是本文最有价值的部分。
依然用例子来讲明:
上接第四节【数据迁移】的例子,咱们来添加一个 V_MyEntity 的视图,代码以下:
1 //我想写一个视图,先别急,先当它是一张表 2 public class V_MyEntity 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 //姓 7 public string First_Name { get; set; } 8 }
1 public class Model1 : DbContext 2 { 3 public Model1() : base("name=Model1") { } 4 public virtual DbSet<MyEntity> MyEntities { get; set; } 5 //我想写一个视图,先别急,先当它是一张表 6 public virtual DbSet<V_MyEntity> V_MyEntity { get; set; } 7 }
而后在 Main 方法随便访问一下这个视图,看看可不能够:
1 using (var db = new Model1()) 2 { 3 //访问一下 V_MyEntity 视图,看看可不能够 4 var x = db.V_MyEntity.Find(1); 5 }
运行程序,天然是报错了:
由于模型与实际数据库表结构不一致,因此报错了,因为数据库中开启了数据迁移的,因此会提示你(要求你)进行数据迁移。
此时,若是按照正常的步骤来进行数据迁移,咱们将获得 V_MyEntity 这个表,这固然不是咱们想要的结果,咱们是要 V_MyEntity 视图啊。
那就须要在数据迁移的过程当中,作一点手脚,具体以下。
在程序包管理器控制台中输入如下命令,以建立一个新的迁移版本:
Add-Migration v2
其中的 v2 表示版本号,其实能够随便乱写也行,只是个版本名字而已。
刚刚的操做使咱们多出一个文件,内容以下(其中的中文注释是我写的,不是生成的):
1 //代码迁移版本:v2 2 public partial class v2 : DbMigration 3 { 4 //版本升级 5 public override void Up() 6 { 7 //建立表 8 CreateTable( 9 "dbo.V_MyEntity", 10 c => new 11 { 12 Id = c.Int(nullable: false, identity: true), 13 Name = c.String(), 14 First_Name = c.String(), 15 }) 16 .PrimaryKey(t => t.Id); 17 18 } 19 //版本降级 20 public override void Down() 21 { 22 //删除表 23 DropTable("dbo.V_MyEntity"); 24 } 25 }
咱们固然不想要建立表啦,咱们要建立视图,那就把这个文件改一改,改为以下这个样子(本文最最最重要的环节):
1 //代码迁移版本:v2 2 public partial class v2 : DbMigration 3 { 4 //版本升级 5 public override void Up() 6 { 7 //建立视图(本文最最最重要的环节) 8 Sql(@"create view V_MyEntity as select id,name,substring(name,1,1) first_name from MyEntities"); 9 } 10 //版本降级 11 public override void Down() 12 { 13 //删除视图 14 Sql("drop view V_MyEntity"); 15 } 16 }
改好以后,再执行数据迁移的最后一个命令,更新数据库:
Update-Database
这个时候,咱们发现数据库没有多出一个 V_MyEntity 的表,而是多出了一个 V_MyEntity 的视图,目的达到。
最后咱们再运行程序,程序也没有再报错了,目的妥妥的达到。
8、在【来自数据库的 Code First】中开启数据迁移
先要复习几个问题:
1) 用【空 Code First 模型】建立的数据库,默认就是开启数据迁移的。
2) 用【来自数据库的 Code First】这种方式,其数据库默认是关闭数据迁移的。
3) 判断一个数据库有没有启用数据迁移,办法是:检查有没有 __MigrationHistory 表。
也许虽然你比较欣赏数据库中开启了数据迁移的这种模式,可是无奈你已经有一个数据库啦,数据库里80多张表也已经建立好啦,那咱们固然要用【来自数据库的 Code First】,来帮助咱们自动生成这80多个实体类啦。
但是,对于一个已经存在的数据库,咱们能开启这个数据库的数据迁移模式吗?答案固然也是能够的,具体操做以下。
好比咱们导入了一个Student表:
1 //我进行了删减 2 public partial class Student 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 }
1 //我进行了删减 2 public partial class Model1 : DbContext 3 { 4 public Model1() : base("name=Model1") { } 5 public virtual DbSet<Student> Student { get; set; } 6 }
而后二话不说,直接在项目中开启代码迁移(请注意这只是在项目中开启代码迁移,并无在数据库中开启代码迁移):
Enable-Migrations
而后再二话不说,建立一个迁移版本:
Add-Migration v1
添加了迁移版本以后,会自动产生一个版本文件,内容以下(中文注释是我写的,不是自动生成的,略也是我删减的):
1 //数据迁移版本名称:v1 2 public partial class v1 : DbMigration 3 { 4 //版本升级 5 public override void Up() 6 { 7 //建表 8 CreateTable("dbo.Student",..略..); 9 } 10 //版本降级 11 public override void Down() 12 { 13 //删表 14 DropTable("dbo.Student"); 15 } 16 }
而后再二话不说,执行更新数据库的操做(这等因而一口气把数据迁移的三个命令挨个打了个遍):
Update-Database
而后就报错了:
错误提示数据库已经有Student这个表了,这个问题怎么解决呢?很好解决,办法就是找到以前那个版本文件,把版本升级Up()方法中的代码所有注释掉,以下:
1 //数据迁移版本名称:v1 2 public partial class v1 : DbMigration 3 { 4 //版本升级 5 public override void Up() 6 { 7 //建表 8 //CreateTable("dbo.Student",..略..); //注释掉这些代码是重要环节 9 } 10 //版本降级 11 public override void Down() 12 { 13 //删表 14 //DropTable("dbo.Student"); //注释掉这些代码是重要环节 15 } 16 }
注释掉以后,再执行 Update-Database ,此次就不报错了。
此时,数据库中多了一个 __MigrationHistory 表,这表示数据库的数据迁移功能已开启,目的已达到:
最后,为了未来考虑,最好将刚刚注释的代码又取消注释,这彻底不影响你以后的使用。
总结:
1) EF 中只有 Code First 模式支持 new Model1(这里写链接字符串),也就是说能够对其进行加密,这使得 Code First 成为大部分状况下的最佳选择。
2) Code First 中,当模型与实际表结构不一致时,程序会报错,咱们有两种方式来使他们一致,一是手动修改模型代码和表结构,二是使用数据迁移功能。
这两种方式,不是你想选哪种就选哪种的,取决于一个关键因素:数据库是否开启了数据迁移
3) 数据迁移的开启,分两个层面,一是项目的数据迁移开启,二是数据库的数据迁移开启,前者并不怎么重要,后者决定了你须要手动仍是自动来使模型与实际表结构一致。
项目中若是包含Migrations文件夹而且里面有相关的类,说明项目已开启了数据迁移。
数据库中若是有 __MigrationHistory 表,说明数据库已开启了数据迁移。
4) 删除数据库中的 __MigrationHistory 表,也就关闭了数据库中数据迁移功能,关闭数据迁移的目的是:
你可能并不熟悉数据迁移是个什么鬼,那不如就让它见鬼去吧。关闭它,会让你进入你熟悉世界,经过手动改写实体类,手动改数据库表结构,来使模型与实际表结构一致,挺好!
5) 个人建议是:不要关闭数据迁移,仍是能够尝试学习一下数据迁移模式的。在程序包管理器控制台下,使用如下三个命令就可使用数据迁移模式了:
*) 在项目中启用数据迁移 Enable-Migrations
*) 建立迁移版本 Add-Migration 版本名称
*) 更新数据库 Update-Database
6) 在数据库关闭数据迁移的状况下,建立视图的方式与建立表同样,写好实体类 V_ABC 以后,再到数据库里建立 V_ABC 的视图就好了。
7) 在数据库开启数据迁移的状况下,建立视图的方式是先写好 V_ABC 实体类,再添加迁移版本,而后在迁移版本的代码中注释建立表 V_ABC 的代码,改成建立视图 V_ABC 的代码,最后执行数据迁移中的更新数据库命令便可。
8) 巨坑:当你在执行数据迁移的三个命令时,它们永远是在 Web.config/App.config 中读取链接字符串!别误觉得是 new Model1(这里的字符串) 啦。