本章学习内容
1.Entity Framework 4.1介绍
2.Entity Framework Code-First 进行数据访问
3.利用EF实现用户的增加和列表功能
1.Entity Framework 4.1介绍
在MX11(微软互联网技术大会)前夕,微软发布了 ADO.NET Entity Framework 4.1 (EF 4.1) 正式版。
EF 4.1有哪些新玩新儿?
1. 首先当然是DbContext API,它是基于以前版本中的ObjectContext和其他一些类型抽象出的一个简单的API,针对常用开发场景和编程模式进行了优化。DbContext可以被于Database First, Model First, Code First三种开发模式。
2. Code First是基于Entity Framework的新的开发模式,原先只有Database First和Model First两种。Code First顾名思义,就是先用C#/VB.NET的类定义好你的领域模型,然后用这些类映射到现有的数据库或者产生新的数据库结构。Code First同样支持通过Data Annotations或fluent API进行定制化配置。
如何拥有EF 4.1?
1. 去微软官方网站下载:ADO.NET Entity Framework 4.1。
2. 在VS2010中通过NuGet将 ‘EntityFramework’ NuGet package 添加到你的项目中,但这个只包含Entity Framework运行时,不包含VS2010文件模板(用于Model First与Database First开发模式)。
如何使用EF 4.1?
可以去下面几个地方逛逛:
1. ADO.NET Entity Framework page on the MSDN Data Developer Center
3. ADO.NET Entity Framework Forum
5. Model First / Database First walkthrough
友情提醒:别忘了去博客园Entity Framework专区。
详情参考http://news.cnblogs.com/n/97213/
很可能你没有用过任何版本的Entity Framework,上面的描述你不太明白,你也不知道那些新特性在以前的版本中是什么样子,没有关系,在我们后续的使用中你会了解他,也会熟练的使用它,享受它的种种特性给你带来的编码便利。
另外说明一下,一开始我就说了我用的是vs2010 sp1,所以我没有额外下载Entity Framework4.1安装包,新建MVC3项目时也没有额外添加该引用,vs已帮我自动完成这些,如果是使用的vs2010还要额外下载安装包并添加引用。
2.利用Entity Framework Code-First 进行数据访问
我们将使用包含在 ASP.NET MVC3 中的 Entity Framework (EF) 支持进行查询和更新数据库中的数据。EF 是一个灵活的进行数据访问的对象关系映射 API,允许开发人员使用面向对象的方式对数据库中的数据进行查询和更新。
Entity Framework 4 支持一种称为代码优先的开发模式,代码有限允许你通过编写简单的类来创建模型对象(也被称为 POCO, 简单的,老的 CLR 对象),然后通过类来创建数据,这与我们传统的应用程序先创建数据库再来编写实体以及后续逻辑处理或许有点稍微不同,不过无所谓,大道同归,让我们先不打开熟悉的SQLSERVER2005数据库,先把vs里的事情做好。
如同以往连接数据库一样,打开web.cofig文件,添加连接字符串,完整web.config代码如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 有关如何配置 ASP.NET 应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=152368 --> <configuration> <connectionStrings> <add name="MyShopDataEntities" connectionString="Data Source=.;Initial Catalog=MyShop;User ID=sa;Password=sa;Integrated Security=true;" providerName="System.Data.SqlClient"></add> </connectionStrings> <appSettings> <add key="webpages:Version" value="1.0.0.0"/> <add key="ClientValidationEnabled" value="true"/> <add key="UnobtrusiveJavaScriptEnabled" value="true"/> <!--此处是上传文件相关配置--> <add key="upFileType" value=".doc|.docx|.rar|.zip|.xls|.xlsx|.ppt|.pptx|.txt|.jpg|.gif|.pdf|.html|.htm|.mht" /> <add key="upImageType" value=".jpg|.gif|.ico|.bmp|.png" /> <add key="upVideoType" value=".avi|.wmv|.mpeg|.rmvb" /> <add key="upFileSize" value="10" /> <add key="upFilePath" value="/UpFile" /> <add key="isWater" value="false" /> <add key="WaterType" value="1" /> <add key="WaterText" value="MVC测试" /> <add key="TextSize" value="12" /> <add key="WaterImg" value="none" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.0"> <assemblies> <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation> <authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication> <membership> <providers> <clear/> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" /> </providers> </membership> <profile> <providers> <clear/> <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" /> </providers> </profile> <roleManager enabled="false"> <providers> <clear/> <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" /> <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" /> </providers> </roleManager> <pages> <namespaces> <add namespace="System.Web.Helpers" /> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="System.Web.WebPages"/> </namespaces> </pages> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules runAllManagedModulesForAllRequests="true"/> </system.webServer> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /> <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
我们在web.config中加入了连接字符串和一些上传文件的配置节点(当然这些与目前的数据库访问没有关系,后续会用到,所以我们一次加了进来),我们加入的数据库连接字符串为“Data Source=.;Initial Catalog=MyShop;User ID=sa;Password=sa;Integrated Security=true;”,这是我的电脑上连接sqlserever2005的字符串,大家请根据自己机器情况加入适宜的连接字符串。
注意:这里的连接字符串的名称很重要,这与我们紧接着要添加的操作数据库色上下文类名称要保持一致,这样这个类才能找到对应的连接字符串来操作数据库。
右键Models,添加数据库操作类,命名为MyShopDataEntities,如图
修改该类完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; namespace MyShopTest.Models { /// <summary> /// 数据访问,类名称必须和数据库连接字符串的name一致,没有数据库的话,系统会根据连接字符串以及下面的数据映射关系,创建对应的类的数据表 /// </summary> public class MyShopDataEntities : DbContext { //实体和数据表的映射 public DbSet<UserInfo> UserInfos { get; set; } } }
对于 Code First 来说,我们首先定义模型,然后通过模型来创建数据库,甚至也不需要写 Insert 语句,我们可以通过标准的 C# 代码来创建表中的记录。
注意,这里使用了 System.Data.Entity 命名空间。记得要 using 一下。不需要其他的配置,特定的接口等等,通过扩展 DbContext 基类我们就能直接操作数据库进行增删改查了。
3.利用EF实现用户的增加和列表功能
用户的摸型实体和数据库操作都有了,我们还缺少控制器和对应的视图,前文说到过在MVC3里,页面展示顺序是从控制器到视图的,那么我们创建这二者的顺序也这样开始,便于理解。
右键Controllers文件夹,添加>控制器,模版选择为空控制器,当然选择其他两项包含读写操作的控制器也可以,甚至更为强大和便捷,不过那是我们以后要选择的,万丈高楼平地起,等我们打好了地基再一个一个收拾,命名为UserInfoController,记住所有的控制器命名都以Controller结尾,点击添加完成
UserInfo控制器创建完成了,我们之前已经列举过控制器的代码了,此次就不再解说了,开头说了我们本章要实现的目标是实现用户增加和列表页面,那么我们先从列表页开始
修改UserInfoController代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MyShopTest.Models; namespace MyShopTest.Controllers { public class UserInfoController : Controller { //数据访问 private MyShopDataEntities db = new MyShopDataEntities(); /// <summary> /// 用户列表Action /// </summary> /// <returns></returns> public ActionResult Index() { var users = db.UserInfos.ToList(); return View(users); } } }
列表的Action有了,他利用EF获取了全部的用户,返回了一个List泛型集合,返回到哪里呢,返回给了View,返回给了ActionResult,最终是将一个List集合返回到了它对应的视图。现在我们来添加对应的视图展示这些数据,右击Index()方法任意处,添加视图
把使用布局或母版页下方选择框里的内容清空,我们直接使用默认的母版页,其他部分包括名称全部默认,后续我们会一一探索这些选项的作用,目前先不管。
视图创建成功,此处直接修改视图代码如下:
@model IEnumerable<MyShopTest.Models.UserInfo> @{ ViewBag.Title = "用户列表"; } <h2> 用户列表</h2> <p> <a href="/UserInfo/Create">添加用户</a> </p> <table> <tr> <th> 用户名 </th> <th> 电话 </th> <th> 邮箱 </th> <th> 注册时间 </th> <th> 操作 </th> </tr> @foreach (var item in Model) { <tr> <td> @item.UserName </td> <td> @item.Phone </td> <td> @item.Email </td> <td> @item.AddTime </td> <td> 暂无操作 </td> </tr> } </table>
Razor视图我们之前已经用来整整一章的内容来讲述它,在此就不一一回顾了,不过毕竟是我们自己亲手添加的第一个完整视图,我们将其中的代码做一分析。
先看
@model IEnumerable<MyShopTest.Models.UserInfo>
头部的这段代码,model是在Razor视图的关键字,通过他允许我们在视图模版中直接访问在控制器类中通过使用强类型的模型“Model”传递过来的数据,换句话说,这个model关键字代表的其实是页面显示的数据来源,它的类型由Action传过来的数据而决定,此处我们用的是IEnumerable,也可以用List,不过IEnumerable是公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代 List ArrayList 等 就实现的该接口,比List更加宽泛一些。
再看
@{ ViewBag.Title = "用户列表"; }
这就不用解释了,表示该页面的标题,如果不设置,默认是母版页的
三看
@foreach (var item in Model) { <tr> <td> @item.UserName </td> <td> @item.Phone </td> <td> @item.Email </td> <td> @item.AddTime </td> <td> 暂无操作 </td> </tr> }
此处代码也不难理解,循环Action传过来的集合,输出值,有一点说明一下,无论是在此处还是在前面的Action里我们都使用了var来作为参数类型,或许有的人会说了,这会导致系统效率的问题,其实不然,可能你会认为使用 var 定义变量使用了迟绑定,这是不正确的,C# 编译器使用赋予变量的值来推定变量的类型,所以并不会导致效率问题,不信我们直接使用var定义的变量看一下,vs的智能提示会直接给出对应类型的提示信息,大家可以试试。
接下来我们来实现我们的添加操作,之后再对代码一一解析,首先修改UserInfoController控制器,添加处理用户添加的Action。修改完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MyShopTest.Models; namespace MyShopTest.Controllers { public class UserInfoController : Controller { //数据访问 private MyShopDataEntities db = new MyShopDataEntities(); /// <summary> /// 用户列表Action /// </summary> /// <returns></returns> public ActionResult Index() { var users = db.UserInfos.ToList(); return View(users); } /// <summary> /// 添加用户页面展示 /// </summary> /// <returns></returns> public ActionResult Create() { return View(); } /// <summary> /// 添加用户处理 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Create(UserInfo user) { db.UserInfos.Add(user); db.SaveChanges(); return RedirectToAction("Index"); } } }
我们给代码添加了两个Action,名称均为Create,我们解释一下这两个Action
第一个
/// <summary> /// 添加用户页面展示 /// </summary> /// <returns></returns> public ActionResult Create() { return View(); }
注释写到,这是展示添加用户页面的Action,他没有任何逻辑处理操作,只是让他返回到对应的视图,我们之前说过,Asp.net MVC里页面处理流程是先访问Action,再回到视图,所以这个Action的作用就是展示添加页面之用。
第二个
/// <summary> /// 添加用户处理 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Create(UserInfo user) { db.UserInfos.Add(user); db.SaveChanges(); return RedirectToAction("Index"); }
代码也相当简练,定义了一个UserInfo类型的参数,db.UserInfos.Add(user)的意思是将user实体添加到EF,user从何而来呢,就是这个方法的参数user,或许这里有些看不明白,传统的webform里我们都需要new一个实体类型,然后依次为里面的字段赋值,每次都市重复这样的过程,MVC里微软帮我们做了这些事,我们定义好实体类型参数,系统会自动一一匹配前台的表单数据,匹配点就是你前台的表单名称要和该实体的属性名一致,这样我们就不需要在使用Request.Form[Key]来取值了。 db.SaveChanges();保存数据更改,这一句代码是必须的,有了这一句应用程序才会将数据写入数据库。return RedirectToAction("Index")的意思是跳转到某个Acton,在这里我们跳到本控制器的Index Action.至于方法头部的HttpPost,表示该方法接受HttpPost请求,他还有HttpGet等其他形式,如果没有写,代表二者都可以处理。
添加Creat视图,右键任意一个Create任意处,添加视图,完整代码如下
@model MyShopTest.Models.UserInfo @{ ViewBag.Title = "添加用户"; } <h2> 添加用户</h2> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm()) {<fieldset> <legend>添加用户</legend> <div class="editor-label"> 用户名 </div> <div class="editor-field"> <input type="text" name="UserName" /> </div> <div class="editor-label"> 密码 </div> <div class="editor-field"> <input type="password" name="UserPwd" /> </div> <div class="editor-label"> 电话 </div> <div class="editor-field"> <input type="text" name="Phone" /> </div> <div class="editor-label"> 邮箱 </div> <div class="editor-field"> <input type="text" name="Email" /> <input type="hidden" name="AddTime" value="@DateTime.Now"/> </div> <p> <input type="submit" value="添加" /> </p> </fieldset> } <div> <a href="Index">返回列表</a> </div>
现在我们来逐一看一下这段代码
@model MyShopTest.Models.UserInfo @{ ViewBag.Title = "添加用户"; }
设置model是UserInfo,设置标题
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
这里面的@Url.Content是虚拟路径转换为程序绝对路径,比如说,程序位于 http://www.abc.com/bbs 下,那么你期待的是 http://www.abc.com/bbs/Content/Site.min.css 不转化,就会成了http://www.abc.com/Content/Site.min.css,这里的Content与程序里的Content文件夹密切相关。
@using (Html.BeginForm())
{
省略期间代码
}
@using (Html.BeginForm())表示使用HtmlHelper 开始一个表单,@Html是一个用于视图中生成 Html 的实用工具,使用它可以保持我们的视图清楚和易读 ,Html.BeginForm()表示开始一个表单,默认提交方式为post,默认提交到当前视图对应的Action,它有很多重载的函数,可以帮助我们手动指定Form提交的参数,比如Html.BeginForm("Create", "StoreManager", FormMethod.Post, new { enctype = "multipart/form-data" }),这表示的就是将飙到提交到StoreManager控制器的Create的Action,并且指定提交方式是Post,使用流的形式提交表单。
到此处,有关用户添加和列表的操作我们已经完成了,我们来测试一下效果,为了我们避免我们自己输入地址栏,请打开_Layout.cshtml共享页面,添加用户管理的链接,完整代码如下
<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> </head> <body> <div class="page"> <div id="header"> <div id="title"> <h1>我的商店</h1> </div> <div id="logindisplay"> 此处是预留注册登陆处 </div> <div id="menucontainer"> <ul id="menu"> <li>@Html.ActionLink("主页", "Index", "Home")</li> <li>@Html.ActionLink("关于", "About", "Home")</li> <li>@Html.ActionLink("用户管理", "Index", "UserInfo")</li> </ul> </div> </div> <div id="main"> @RenderBody() </div> <div id="footer"> </div> </div> </body> </html>
运行项目,点击用户管理
点击添加用户,输入信息,点击添加
系统自动返回列表页,数据已经出现。
到此为止我们已经实现了数据库的增加和查询操作,但是到目前为止我们甚至连数据都没有打开过,这在以前的传统Webform里几乎是不可能的,事实是我们做到了,我们不得不佩服Entity Framework的强大,也不得不佩服微软的技术实力及其前瞻性。建议大家回顾一下代码,从Model到Controller到Action到View,假如你用过java,了解java的SSH框架,或许理解会更快一些,SSH中的数据库操作是通过Hibernate来操作的,不过可能会需要配置一些复杂的xml映射关系,在保存数据时比Asp.net更早的实现了获取一个Fom直接来保存,当然前提是jsp里必须要一一对应表单数据,这些操作换到asp.net MVC上,所有这些操作微软几乎都帮我们完成了。
末了,我们再看一下,我们之前的代码帮我们创建了什么样的数据库,打开Sqlserver2005,打开连接字符串的数据库
我们可以看到,系统自动帮我们创建了MyShop数据库,建立好了UsrtInfo表,所有的字段都和UserInfo Entity对应好了,主键也已设置,是不是很神奇?我们来解释一下这个过程,EF通过连接字符串来访问数据库,如果没有检测到数据库系统会根据连接字符串创建对应的数据库,然后再检测数据库上下文类里的实体信息,创建对应的映射表。过程就是这样,或许大家还有疑问,EF是怎样区分主外键的?外键的问题我们后续创建商品相关操作时会涉及到,暂时先来看看主键,根据约定,系统会查找实体模型类里是否存在Id或者类名+Id的属性,如果存在,将会以此为主键,如果不存在,则会报错,有兴趣的可以试试。当然我们也可以自由指定主键,这个也在后续了解。