MVC3学习第八章 葵花点穴手之指如疾风----MVC3下实现数据的批量删除和利用EF初始化我们的数据
1.本章学习内容
1.实现用户信息的批量删除
2.实现用户数据的初始化以及当实体发生改变时自动更改数据库
1.实现用户信息的批量删除
在前面几章里我们已经完成了用户信息的添加,修改,列表展示,现在我们来完成删除功能。
首先,修改UserInfo控制器添加用书删除的处理Action,完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MyShopTest.Models; using System.Data; 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"); } /// <summary> /// 编辑用户页面展示 /// </summary> /// <returns></returns> public ActionResult Edit(int id) { var user = db.UserInfos.Find(id); return View(user); } /// <summary> /// 编辑用户处理 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Edit(UserInfo user) { try { if (ModelState.IsValid) { db.Entry(user).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } else { throw new Exception(); } } catch (Exception ex) { ModelState.AddModelError("", "更改失败"); } return View(user); } /// <summary> /// 删除操作处理 /// </summary> /// <returns></returns> public ActionResult Delete(int id) { var user = db.UserInfos.Find(id); db.UserInfos.Remove(user); db.SaveChanges(); return RedirectToAction("Index"); } } }
新加的代码做一个简单解析。
var user = db.UserInfos.Find(id);的意思是根据传过来的用户id查找对应用户;
db.UserInfos.Remove(user);的意思是移除用户
按照我们的一般思维逻辑,删除操作一般在列表页实现,通常会有一个确认删除的提示,现在我们修改UserInfo/Index.cshtml代码,添加删除链接和确认删除的提示js,完整代码如下:
@model IEnumerable<MyShopTest.Models.UserInfo> @{ ViewBag.Title = "用户列表"; } <script type="text/jscript"> function confirmDel(itemId) { if (confirm("确认删除吗?")) { window.location.href = "/UserInfo/Delete/" + itemId; } } </script> <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> @Html.ActionLink("编辑", "Edit", new { id=item.Id}) <a href="javascript:void(0);" onclick="confirmDel(@item.Id);">删除</a> </td> </tr> } </table>
这些代码里,没有新知识,因此不做解析。重新编译项目,运行,进入用户管理。
点击删除,可以看到确认删除的提示
点击确定,删除操作完成
现在我们已经基本完成了用户的删除操作,但是有些时候当用户数据很多的时候,我们更期望进行批量删除操作,这也是在传统的B/S应用程序里经常遇到的,现在我们再来为用户管理,添加批量删除功能,同时保留刚才的逐项删除,以帮助大家理解代码,修改UserInfo控制器,添加批量删除的处理Action,完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MyShopTest.Models; using System.Data; 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"); } /// <summary> /// 编辑用户页面展示 /// </summary> /// <returns></returns> public ActionResult Edit(int id) { var user = db.UserInfos.Find(id); return View(user); } /// <summary> /// 编辑用户处理 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Edit(UserInfo user) { try { if (ModelState.IsValid) { db.Entry(user).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } else { throw new Exception(); } } catch (Exception ex) { ModelState.AddModelError("", "更改失败"); } return View(user); } /// <summary> /// 删除操作处理 /// </summary> /// <returns></returns> public ActionResult Delete(int id) { var user = db.UserInfos.Find(id); db.UserInfos.Remove(user); db.SaveChanges(); return RedirectToAction("Index"); } /// <summary> /// 批量删除操作处理 /// </summary> /// <param name="coll"></param> /// <returns></returns> [HttpPost] public ActionResult Deletes(FormCollection coll) { string ids = coll["ckSelect"]; foreach (var item in ids.Split(','))//循环每一项Id { if (item != "false")//筛选掉自动生成的checkbox初始值 { var user = db.UserInfos.Find(Convert.ToInt32(item)); db.UserInfos.Remove(user); } } db.SaveChanges(); return RedirectToAction("Index"); } } }
解析一下新增代码,
参数(FormCollection coll),这是获取提交过来的表单值集合
string ids = coll["ckSelect"];获取表单中name是ckSelect的所有表单值,获取到的是英文逗号分隔的一个数组
foreach (var item in ids.Split(','))循环每一个值
if (item != "false")//筛选掉自动生成的checkbox初始值,因为我们前台使用了 @Html.CheckBox来生成复选框,而这个方法在生成复选框时会默认生成该复选框的一个Hidden标签,用来保存该复选框的初始状态,详见接下来的视图页面注解。
现在,我们修改视图页面以实现批量删除,仍是修改Index.cshtml,完整代码如下:
@model IEnumerable<MyShopTest.Models.UserInfo> @{ ViewBag.Title = "用户列表"; } <script src="../../Scripts/jquery-1.5.1-vsdoc.js" type="text/javascript"></script> <script type="text/jscript"> function confirmDel(itemId) { if (confirm("确认删除吗?")) { window.location.href = "/UserInfo/Delete/" + itemId; } } function selectAll() { var checked = $("#ckAll").attr("checked"); $("input[name='ckSelect']").attr("checked", checked); if (checked) { $("#spInfo").html("反选"); } else { $("#spInfo").html("全选"); } } function delIds() { if (confirm("确认要删除选中数据吗?")) { var checkedCount = $("input[name='ckSelect']:checked").length; if (checkedCount>0) { $("form").first().submit(); //提交表单 } else { alert("请先选择操作项!"); } } } </script> <h2> 用户列表</h2> @using (Html.BeginForm("Deletes", "UserInfo", FormMethod.Post)) { <p> <a href="/UserInfo/Create">添加用户</a> <input type="button" onclick="delIds();" value="批量删除" /> </p> <table> <tr> <th> <input type="checkbox" name="ckAll" id="ckAll" onclick="selectAll();" /><span id="spInfo">全选</span> </th> <th> 用户名 </th> <th> 电话 </th> <th> 邮箱 </th> <th> 注册时间 </th> <th> 操作 </th> </tr> @foreach (var item in Model) { <tr> <td> <!--此方法生成的复选框,会默认生成一个对应的hidden,用来保存初始状态,此方法参数意义是,名称,初始选中状态,html属性--> @Html.CheckBox("ckSelect", false, new { value = item.Id }) </td> <td> @item.UserName </td> <td> @item.Phone </td> <td> @item.Email </td> <td> @item.AddTime </td> <td> @Html.ActionLink("编辑", "Edit", new { id = item.Id }) <a href="javascript:void(0);" onclick="confirmDel(@item.Id);">删除</a> </td> </tr> } </table> }
我们添加了一些js代码,用以实现全选和确认删除,我们头部引用了
<script src="../../Scripts/jquery-1.5.1-vsdoc.js" type="text/javascript"></script>这是为了实现在vs下的jquery的智能提示,jquery引用类库,其实是在母版页里。
重新编译项目,运行,用户管理,自行添加几条测试数据:
右键,查看源文件,我们可以看到 @Html.CheckBox生成有隐藏域保存初始状态
我们来测试一下全选效果:
我们去掉一项,准备执行删除
点击批量删除,会弹出提示是否确认
点击确认,完成批量删除
至此,我们已经完成了用户的批量删除功能。
2.实现用户数据的初始化以及当实体发生改变时自动更改数据库
上一章里我们给用户信息添加验证时,重新编译项目运行时项目出错了,提示实体已经改动,我们当时为了测试方便的做法是,直接删除了原有数据库,重新让系统创建的。虽然当时解决了问题,但是这种做法有诸多不足,比如当项目已经进行到了相当的程度,或者项目里已经有不少的测试数据了,而且测试数据的关联性又比较强,这时候我们再清除数据就相当麻烦了,但是在实际的项目开发中,用户变更需求,或者程序员由于当时的设计不够健壮,后续改动实体模型和数据库结构的情况很多,那么怎么才能避免这种情况,使得数据库即使重建了也能保留好我们的测试数据,或者干脆在实体发生改动时就自动重构数据库呢。接下来我们就来实现这个功能。
右键Models文件夹,添加类,命名为InitData,修改完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; using System.Data.Entity.Validation; namespace MyShopTest.Models { public class InitData : DropCreateDatabaseIfModelChanges<MyShopDataEntities> { //实体发生改变时,重新创建数据库,首次也会运行此处 protected override void Seed(MyShopDataEntities context) { try { //初始化数据 var users = new List<UserInfo> { new UserInfo { UserName = "admin",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" }, new UserInfo { UserName = "zhangsan",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" }, new UserInfo { UserName = "lisi",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" } }; users.ForEach(user => context.UserInfos.Add(user)); } catch (DbEntityValidationException dbEx) { string a = dbEx.Message; } } } }
我们来解析一下代码:
public class InitData : DropCreateDatabaseIfModelChanges<MyShopDataEntities>
这句代码的意思是该类继承于DropCreateDatabaseIfModelChanges,DropCreateDatabaseIfModelChanges是当实体改变时重新构建数据库的类,它指明的数据库操作类是我们建立好的MyShopDataEntities,这样当实体发生变化时程序才知道根据哪个类去操作数据库。
//实体发生改变时,重新创建数据库,首次也会运行此处 protected override void Seed(MyShopDataEntities context) { try { //初始化数据 var users = new List<UserInfo> { new UserInfo { UserName = "admin",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" }, new UserInfo { UserName = "zhangsan",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" }, new UserInfo { UserName = "lisi",UserPwd="admin",AddTime=DateTime.Now,Email="Qq@qq.com",Phone="13112345678" } }; users.ForEach(user => context.UserInfos.Add(user)); } catch (DbEntityValidationException dbEx) { string a = dbEx.Message; } }
我们重写了DropCreateDatabaseIfModelChanges里的Seed方法,这个方法表明我们要初始化的数据,此处我们用三个用户组成了一个集合,然后一一计入了数据库。
到此处,我们写的实体发生改变时初始化的数据已经完成了,我们还需要告诉程序什么时候执行这一个类,这就需要用到Global,双击打开Global.asax.cs,修改完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using MyShopTest.Models; namespace MyShopTest { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 ); } protected void Application_Start() { //System.Data.Entity.Database.SetInitializer<MyShopDataEntities>(null);//此段代码实现同时修改表和类中字段,而数据不动。 System.Data.Entity.Database.SetInitializer(new InitData());//此段代码实现重新根据新实体模型构建数据库,同时初始化数据 AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } } }
添加了两行代码,注释应该已经说的很清楚了,在此不再解析,为了测试效果,我们修改Models/UserInfo的属性Phone,将它变为不必必须输入,修改后完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; namespace MyShopTest.Models { public class UserInfo { [ScaffoldColumn(false)] public int Id { get; set; } [Display(Name="用户名")] [Required(ErrorMessage="必须输入用户名!")] public string UserName { get; set; } [Display(Name = "密码")] [Required(ErrorMessage = "必须输入密码!")] [StringLength(8, MinimumLength = 3, ErrorMessage = "密码长度必须是3到8个字符之间")] public string UserPwd { get; set; } [Display(Name = "电话")] [RegularExpression(@"((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)",ErrorMessage="电话格式不正确")] public string Phone { get; set; } [Display(Name = "邮箱")] [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = "邮箱格式不正确")] public string Email { get; set; } private DateTime addTime; [Display(Name="注册时间")] [Required()] public DateTime AddTime { get { if (addTime == null) { return DateTime.Now; } return addTime; } set { addTime = value; } } } }
重新编译项目,运行,看一下测试效果
初始化数据已经添加了,测试成功