这是上周就写好的文章是在公司浩哥的建议下写的本来是部门里面分享求创新用的这里贴出来分享给大家。
最近在对MVC的学习过程中接触到了Code First这种新的设计模式感觉很新颖并且也体验到了这种方式所带来的便利。这里将我的一些理解分享出来。
需要了解的概念
POCO
POCOPlain Old CLR Object的概念是从java的POJO借用而来而两者的含义是一致的不同的仅仅是使用的语言不一样。所以POCO的解释就是“Plain Old C# Object”。POJO的内在含义是指那些没有从任何类继承、也没有实现任何接口更没有被其它框架侵入的对象。
PO
PO是指持久对象persistant object持久对象。持久对象实际上必须对应数据库中的entity所以和POJO有所区别。比如说POJO是由new创建由GC回收。但是持久对象是 insert数据库创建由数据库delete删除的。基本上持久对象 生命周期和数据库密切相关。另外持久对象往往只能存在一个数据库 Connection之中Connnection关闭以后持久对象就不存在了而POJO只要不被GC回收总是存在的。
ORM
ORM(Object/Relational Mapping) 对象关系映射主要是把数据库中的关系数据映射称为程序中的对象
NHibernate
NHibernate是一个面向.NET环境的对象/关系数据库映射工具。对象/关系数据库映射object/relational mappingORM这个术语表示一种技术用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。所以NHibernate与Entity Framework是很相近的。
Entity Framework
Entity Framework的全称是ADO.NET Entity Framework是微软开发的基于ADO.NET的ORM(Object/Relational Mapping)框架。
其架构图如下
在接手一个新项目时我们所熟知及习惯使用的设计方式是在分析需求后开始创建数据库中的表一旦表确定后变动不会太大或者几乎不再去更改表的结构后面的模型编写及业务逻辑的编写都在这个基础上进行。这种方式称为Database First。
而在2011四月发布的Entity Framework 4中存在三种工作方式他们分别为Code First, Model First和Database First。其中本文要讨论的Code First就是新增的一种方式。
Code First
为了支持以设计为中心的开发流程EF4 还更多地支持以代码为中心 (code-centric) 我们称为代码优先的开发代码优先的开发支持更加优美的开发流程它允许你
在不使用设计器或者定义一个 XML 映射文件的情况下进行开发。
·允许编写简单的模型对象POCO (plain old classes)而不需要基类。
·通过"约定优于配置"使得数据库持久层不需要任何的配置
·也可以覆盖"约定优于配置"通过流畅的 API 来完全定制持层的映射。
Code First是基于Entity Framework的新的开发模式原先只有Database First和Model First两种。Code First顾名思义就是先用C#/VB.NET的类定义好你的领域模型然后用这些类映射到现有的数据库或者产生新的数据库结构。Code First同样支持通过Data Annotations或fluent API进行定制化配置。
其他两种设计方式
Database First是最老也是应用得最广泛的一种设计方式。如上文提到过的那样Database First这种方式的设计高度依赖于数据库中表的结构根据表及表间的关系来创建模型。如果后期需求有所变更或者功能有很大变化的话需要涉及到更改数据库所付出的代价将会很大因为之前编写好的代码将不再适用于新的表我们必需重构以更改代码中的逻辑以适应更改之后的表。
Model Firstj是创建ADO.NET实体对象以及它们之间的关系然后再指定到数据库的映射。这个实体对象即为Model。
在MVC中使用CodeFirst
这里我将用一个非常简单的例子来演示一下Code First在MVC中的使用主要还是展示Code First 具体是怎么工作的。
Note:你可能需要单独安装Entity Framework 4.1
ADO.NET Entity Framework 4.1 安装文件下载地址http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=8363
下面我们开始这个例子。既然是Code First就是以代码为中心进行设计所以我们并不关心具体的数据库构造到底是怎么样的。我们从分析需求开始即我们要实现的功能。这里我仅仅是要实现一个展示博客列表的页面这里我们不关注CRUD的实现因为这些都可以在MVC里自动生成。这里我们将看到整个过程我们没有写sql语句也没有用sql server management studio 在数据库里进行表的设计但我们运行程序时这些需要的表就已经自动创建好了并且我们在页面进行CRUD操作时也是与数据库里面的数据是完全同步的。
打开VS2010新建一个MVC3项目注意环境要设为.Net Framework 4。这里我将项目命名为MvcBlog。
选择空的模板View engine我们用ASPX。
因为Code First需要EntityFramework支持项目创建好后在引用里面系统已经自动为我们引用了EntityFramework程序集如果没有需要手动引用进来。
为了展示完整的目录层结构在图片上我把中间很多引用去掉换以省略号代替所以你看到这张图片时不要感到奇怪我的VS不是特别定制版的
我们首先在Model中创建一个博客类来保存一篇博客的基本信息它包含一个博客标识BlogId, 标题Title还有创建日期CreateDate。
在Model文件夹上右击选择Add->Class。
在Name中输入Blog再点击Add。
其中的代码如下
1 namespace MvcBlog.Models 2 3 { 4 5 public class Blog 6 7 { 8 9 public int BlogId { get; set; } 10 11 public string Title { get; set; } 12 13 14 15 public DateTime CreateDate { get; set; } 16 17 } 18 19 }
同样的方法我们再新建一个类命名为BlogEntities.cs,这个类跟Blog类的功能是不同的我们将在代码里将它定义为从DBContex继承这样这个BlogEntities上下文类将扮演着与数据库沟通的重要角色在这里面可以定义需要的表项目运行后这些表将自动在数据库创建。这里我们用刚才定义好的Blog来定义一个Dbset代码如下
1 public class BlogEntities:DbContext 2 3 { 4 5 public DbSet<Blog> Blogs { get; set; } 6 7 }
注意这里需要在文件顶部添加using System.Data.Entity引用语句才能使用DbContext及DbSet
上面我们把模型准备好了现在定义Controller。但在进行这一步前我们需要Ctrl+Shift+B编译一下程序以便系统知道我们之前定义好的Blog类和BlogEntities类。右击Controllers文件夹选择Add->Controller打开添加Controller对话框将其命名为HomeControllerMVC的Controller,当然你也可以随便命名只不过在打开网页时需要手动输入路径此刻数据库还没有被创建并且呆会自动创建后里面也是空的没有数据我们需要向里面添加一些数据所以这里模板我们选择带有CRUD增删除改查功能的再将下面的模型类选为Blog,上下文类选为BlogEntities系统会自动实现CRUD操作不用我们手动编写。
注意之前必需编译一下才能在这一步的下拉菜单里找到我们已经定义好的类
单击Add之后生成的代码大概是这样
namespace MvcBlog.Controllers { public class HomeController : Controller { private BlogEntities db = new BlogEntities(); // // GET: /Home/ public ViewResult Index() { return View(db.Blogs.ToList()); } // // GET: /Home/Details/5 public ViewResult Details(int id) { Blog blog = db.Blogs.Find(id); return View(blog); } // // GET: /Home/Create public ActionResult Create() { var blog = new Blog(); return View(); } // // POST: /Home/Create [HttpPost] public ActionResult Create(Blog blog) { if (ModelState.IsValid) { db.Blogs.Add(blog); db.SaveChanges(); return RedirectToAction("Index"); } return View(blog); } // // GET: /Home/Edit/5 public ActionResult Edit(int id) { Blog blog = db.Blogs.Find(id); return View(blog); } // // POST: /Home/Edit/5 [HttpPost] public ActionResult Edit(Blog blog) { if (ModelState.IsValid) { db.Entry(blog).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(blog); } // // GET: /Home/Delete/5 public ActionResult Delete(int id) { Blog blog = db.Blogs.Find(id); return View(blog); } // // POST: /Home/Delete/5 [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { Blog blog = db.Blogs.Find(id); db.Blogs.Remove(blog); db.SaveChanges(); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
视图已经自动添加在了Views文件夹下到此我们的程序已经能够工作了虽然我们似乎什么都没做。在运行前我们可以打开Sql server management studio 或者在VS里用服务器管理器连接到你本地的数据库查看一下以确定数据库里还没有我们程序里需要的Blog表。然后我们Ctrl+F5运行网站。
页面显示出了我们在Blog中定义的Title字段和CreateDate字段但由于还没有数据来进行显示所以只有标题。
这个时候我们断开数据库再重新连接就会发现EF创建了一个程序命名空间为名称的数据库MvcBlog.Models.BlogEntities里面包含我们用Dbset定义的Blogs表。表中的列也正好与我们在Blog类中定义的字段相对应。
仔细观察你会发现它自动将BlogId定义为了主键这是EF的convention在起作用如果我们没有显示地指定数据库的连接字符串等配置信息这些值将会遵从EF里的约定进行取值比如这里的数据库名 MvcBlog.Models.BlogEntities。
这样的名称当然不是我们想要的而且如果我们本地安装了多个数据库实例的话我们也希望指定项目将数据创建在我们想要的实例当中。要覆盖默认的约定我们只需在Web.config注意不是Views文件夹下的Web.config里添加一下对数据库的配置信息代码如下
<connectionStrings> <add name="BlogEntities" connectionString="server=(local)\sqlexpress;database=MvcBlog;integrated security=true;" providerName="System.Data.SqlClient"/> </connectionStrings>
注意上面的name应和Models中的定义的上下文类名称相同这样才能使EF正常工作。server换成你本机数据库实例的名字。现在我们可以看到在先前那个数据库上方它生成了我们重新命名的数据库。
重新运行程序我们在页面中点击Create New来添加几条数据。
添加完数据后我们再返回去看数据库中的变化。
如图数据库中已经保存了我们在页面上添加的数据。
到这里大家已经看到了Code First 设计方式的大概过程。现在我们来回顾一下之前所做的操作将会更加明白这一过程是怎样进行的特别是代码优先是如何做到在没有数据库支持的情况下先建立数据模型然后再对数据库进行操作的。
首先我们在Models中创建了所需要的数据模型Blog类里面包含的字段将映射到以后数据库表中的相应列。
接下来同样是在Models文件夹中我们定义了一个最关键的BlogEntities上下文类它继承自System.Data.Entity下的DbContext它将我们的数据模型映射到数据库中将代码中的数据持久化。
最后在页面上进行添加数据时页面将表单数据通过Blog类型传回Controller里面相应的方法这里是处理Post回传的Create Action,它接收传回来的数据通过调用db.SaveChanges()进行了数据的保存。这里db是BlogEntities上下文类的一个实例。通过BlogEntities上下文类我们进行的RUD操作将反应到数据库中完成了从代码到数据库的更新过程。
Data Annotations
接着上面的例子试想一篇博客一般还包含有分类信息比如日志随笔等。现在我们就去添加这样一个类保存博客的分类信息相应地我们还应更新一下我们的博客类让它包含一个分类属性这样我们就可以指定一篇博客的所属分类了。
这里将看到如何通过Code First创建表间的外键关系约束以及定义表中列的其他信息比如显示的信息
我们在Models文件夹中继续添加一个名为Category的类它包含一个分类标识属性CategoryId, 一个分类名属性CategoryName。代码如下
1 namespace MvcBlog.Models 2 3 { 4 5 public class Category 6 7 { 8 9 public int CategoryId { get; set; } 10 11 public string CategoryName { get; set; } 12 13 } 14 15 }
更新Blog类
1 namespace MvcBlog.Models 2 3 { 4 5 public class Blog 6 7 { 8 9 public int BlogId { get; set; } 10 11 public string Title { get; set; } 12 13 public DateTime CreateDate { get; set; } 14 15 public int CategoryId { get; set; } 16 17 } 18 19 }
我们向其中添加了一个CategoryId属性下面设置它为引用自Category中CategoryId的外键。
这里有两个方法Data Annotation 和 Fluent API用于实现这种到数据库的映射这里只讨论Data Annotation。
由于CategoryId将会成为Blog到Category的导航属性所以除了要在Blog中添加CategoryId外还需要添加一个类型为Category的属性这样在Blog中才会有一个来自Category中CategoryId的声明。
1 public class Blog 2 3 { 4 5 public int BlogId { get; set; } 6 7 public string Title { get; set; } 8 9 public DateTime CreateDate { get; set; } 10 11 public int CategoryId { get; set; } 12 13 public Category Category { get; set; } 14 15 }
在使用Data Annotation添加外键属性前需要添加System.ComponentModel.DataAnnotations命名空间到Blog类。然后我们就可以在CategoryId上面添加一个外键属性了。更改后的代码如下
public class Blog { public int BlogId { get; set; } public string Title { get; set; } public DateTime CreateDate { get; set; } [ForeignKey("CategoryId")] public int CategoryId { get; set; } public Category Category { get; set; } }
同时更新我们的BlogEntities对象
1 public class BlogEntities:DbContext 2 3 { 4 5 public DbSet<Blog> Blogs { get; set; } 6 7 public Category Categories { get; set; } 8 9 }
这时当我们运行程序时会报错如图
因为之前运行程序时已经创建了数据库了而现在我们在对模型进行更改后它无法完全将更改之后的模型映射到之前的数据库所以会出错。从错误提示中已经给出了解决办法。要么手动删除之前创建好的数据库要么使用DropCreateDatabaseIfModelChanges 的一个实例来对数据库进行初始化。需要注意的一点是在商业开发中第二种方法要小心使用因为它会把之前的数据库自动删掉重新创建而如果你之前保存有大量信息在里面的话将无法挽回。
这里我们不想每次在修改模型之后都手动去删除所以用第二种方法将十分简便只需到Global.asax文件的Application_Start方法里面添加如下一行即可。
1 protected void Application_Start() 2 3 { 4 5 Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BlogEntities>()); 6 7 AreaRegistration.RegisterAllAreas(); 8 9 10 11 RegisterGlobalFilters(GlobalFilters.Filters); 12 13 RegisterRoutes(RouteTable.Routes); 14 15 }
这样模型改变之后它会删除掉原来的数据库重新创建。
我们再次运行程序然后再去数据库看第二张表Category已经添加进去了并且在Blog表里面也出现了一个CategoryId外键正如我们所想要的那样。
需要注意的地方
一个就是性能问题使用edm designer从数据库来生成的ObjectContext它会把很多东西存在CSDL,MSL,SSDL文件里比如一些mapping信息当页面load时需要先从EDM文件里来load这些metadata,而使用Code-first是从Assembly里load数据这样应该会节省不少时间。但另一方面EF自动生成的CONTEXT实体类会附带很多冗余信息使得文件相对臃肿在读取实体类中的信息时会在搜索上耗费不必需的时间。关于性能问题具体可阅读一下参考中的第一篇文章。
另外就是现在Code First应用得不是很多就现在学习中碰到的例子来看大部分都是Database First方式的。由于这是伴随微软新一代的EF4.1推出的设计方式必定有其优越性所以以后的项目中还是可以尝试用这种方式来进行设计。
后记
我也是初学者研究得不是很深刻借鉴了许多别人的经验与分享再加上自己的创新理解肯定有不恬当的地方权当加深自己的理解与大家共同学习。
可以参考的一些文章
1.WCF和Entity framework 发现的性能问题
http://www.cnblogs.com/GaryChen/archive/2010/02/06/1664912.html
2.Entity Framework 4中的Code-First, Model-First和Database-First模式
http://www.cnblogs.com/n-pei/archive/2010/08/12/1797751.html
3.Code-First Development with Entity Framework 4
4.ADO.NET Entity Framework
http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework
5.A Code First Example using Entity Framework 4
http://chris.widdowson.id.au/?p=746#
6. Entity Framework At-a-Glance
http://msdn.microsoft.com/en-us/data/aa937709
7. Nadege Deroussen在codeproject的三篇文章
Entity Framework Code First: Let's Try It
http://www.codeproject.com/Articles/318010/Entity-Framework-Code-First-Let-s-Try-It
EF Code First: Add a Foreign Key relationship
http://www.codeproject.com/Articles/319366/EF-Code-First-Add-a-Foreign-Key-relationship
EF Data Annotations and Code Fluent
http://www.codeproject.com/Articles/368164/EF-Data-Annotations-and-Code-Fluent