MVC3学习第九章 葵花点穴手之势如闪电----MVC3下实现用户信息的查询以及通过实体模型建立商品和类别的主外键关系
本章学习内容
1.实现用户信息查询(了解命名参数和可选参数知识)
2.建立商品实体,商品类别实体和添加相关的数据库操作;
1.实现用户信息查询(了解ViewResult和ActionResult异同,了解命名参数和可选参数知识)
在前面几章,一个简单但较为完善的用户管理我们已经基本完成了,但是还缺少一个用户查询,在用户数量较多的时候,这是一个必不可少的功能,现在,我们来修改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> /// 用户查询页面展示Action /// </summary> /// <returns></returns> public ActionResult Search() { var users = db.UserInfos.ToList(); ViewBag.KeyName = ""; return View(users); } /// <summary> /// 用户查询Action /// </summary> /// <returns></returns> [HttpPost] public ActionResult Search(string name) { var users = db.UserInfos.Where(user=>user.UserName.IndexOf(name)>-1); ViewBag.KeyName = name;//保存查询的name,以便在页面显示 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"); } } }
虽然添加了Search方法,其实真正起作用的也就是一行代码:
var users = db.UserInfos.Where(user=>user.UserName.IndexOf(name)>-1);
这句代码的意思是查询用户名符合输入参数的结果集,IndexOf(name)>-1意思是实现的是模糊查询,只要用户名中含有输入的参数即可,这是典型的用c#语法来实现数据库查询操作,
至于输入参数就是参数string name了,前面已经有过这样的用发了,没有什么高深的逻辑,在此做一个简单的解释,只要前台视图传递过来的请求存在名为name的参数,系统就会自动匹配赋值给它,而不用我们再写string name=Request.Params["name"]这样的语句来赋值接收,当然你非要写也可以,除非特殊需要这种变态需求我们暂不考虑。
用户查询的Action写完了,我们添加对应视图,完整代码如下:
@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; } } </script> <h2> 用户查询</h2> @using (Html.BeginForm()) { <p> <a href="/UserInfo/Create">添加用户</a> </p> <p> <input type="text" name="name" value="@ViewBag.KeyName" /> <input type="submit" value="查询" /> </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> }
给用户列表(Index.cshtml)加上用户查询的链接,重新编译项目,运行,进入用户查询,测试结果:
一个简单的查询貌似我们已经完成了,之所以要说貌似,是因为目前的查询功能问题还比较多,比如,什么也不输入,点击查询:
结果为空,这显然不符合实际,再比如我们不仅需要按用户名查询我们还需要按电话查询,或许你会说这些都没有问题,我们可以修改我们的Action来实现,是的,是这样,我们可以修改代码来实现,我们事实上也会这么做,问题重点是,如何用最简略的代码来更优雅的实现。还有一个最大的问题,或许聪明的你已经发现我们这个查询有些与众不同,因为通常实际中我们的查询适合数据列表在一起的,查询存在的意义大多也是希望可以精确的定位要找的数据,然后对他们进行操作,我们目前的查询只是相当于复制了一遍Index的代码,稍微做了改动。
现在我们来总结一下目前的查询的问题:
1).有bug存在,未输入字符串查询为空;
2).功能不够完善,还需要按照电话号码查询;
3).代码冗余太多,Search的实现过程无论是Action还是视图都与Index大篇幅相同;
4)功能实现与实际有偏差
为了修正以上问题,我们重新修改我们的Search实现,请先按照我的描述来操作,稍后我们贴出代码来解析
首先,修改UserInfoController,完整代码如下;
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(string name = "", string phone = "13112345678")//可选参数 { var users = SearchUser(phones: phone, names: name);//命名参数,命名参数必须出现在所有固定参数之后 ViewBag.Name = name; ViewBag.Phone = phone; return View(users); } /// <summary> /// 实现用户查询 /// </summary> /// <param name="name"></param> /// <param name="phone"></param> /// <returns></returns> public IEnumerable<UserInfo> SearchUser(string names, string phones) { if (string.IsNullOrEmpty(phones)) { phones = "13112345678"; } var users = db.UserInfos.Where(user => user.Id > 0);//此处的查询没有任何意义,只是返回所有用户 if (!string.IsNullOrEmpty(names)) { users = users.Where(user => user.UserName.IndexOf(names) > -1); } users = users.Where(user => user.Phone == phones); return 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"); } } }
对比一下之前的代码,我们可以看到,经过修改后的控制器代码删除了两个Search的Action,添加了SearchUser方法,并且修改了Index Action来加入查询功能,我们来分析一下SearchUser方法;
public IEnumerable<UserInfo> SearchUser(string names, string phones) { if (string.IsNullOrEmpty(phones)) { phones = "13112345678"; } var users = db.UserInfos.Where(user => user.Id > 0);//此处的查询没有任何意义,只是返回所有用户 if (!string.IsNullOrEmpty(names)) { users = users.Where(user => user.UserName.IndexOf(names) > -1); } users = users.Where(user => user.Phone == phones); return users; }
该方法有两个参数,一个是names,一个是phone,分别代表用户输入查询的姓名和电话,我们先判断一下假如输入的电话是空,则给与默认值为13112345678,这并不是一个特殊的代码,因为目前我的所有测试数据用户电话都是13112345678,只是为了保证没有输入电话时,也会有满足电话的条件,
var users = db.UserInfos.Where(user => user.Id > 0);
这一句代码注释已经写过了,没有任何实际意义,可以看到给的查询条件也必定会返回所有用户,为的只是使得var users的类型和后续我们要进行查询的类型保持一致,这里不得不提一下我们使用var给我们带来便利的同时,也要注意的地方,
1).使用var时不能先声明后赋值,比如
var a; a = "a";
这是错误的,必须是这样
var a = "a";//必须是这样
2.var的类型一经声明使用不可更改,这也是我们使用var users = db.UserInfos.Where(user => user.Id > 0)的原因;
接下来的查询就不用解释了,我们再来看看Index Action里的调用
public ActionResult Index(string name = "", string phone = "13112345678")//可选参数 { var users = SearchUser(phones: phone, names: name);//命名参数,命名参数必须出现在所有固定参数之后 ViewBag.Name = name; ViewBag.Phone = phone; return View(users); }
第一行代码大家或许没有见过
(string name = "", string phone = "13112345678")//可选参数
这是可选参数的应用,意思是如果没有给出该参数,我们给他一个默认值,比如我们等会运行项目时,直接访问用户列表页面,没有点击查询,那么也不用担心报参数异常的错误,因为我们给了默认值。
var users = SearchUser(phones: phone, names: name);//命名参数,命名参数必须出现在所有固定参数之后
这一句是命名参数的应用,数是把参数附上参数名称,这样在调用方法的时候不必按照原来的参数顺序填写参数,只需要对应好参数的名称也能完成方法。
注:命名参数如果只是改变参数的顺序,这样的意义并不大,我们没有必要为了改变顺序而去用命名参数,他与可选参数结合才能显示出他真正的意义,本文只是测试功能仅做参考,不代表命名参数意义只是为了改变顺序
其他的就不解释了。
我们删除Search视图,修改Index视图,完整代码如下:
@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("请先选择操作项!"); } } } function search() { $("form").attr("action", "/UserInfo/Index").submit(); //更改表单提交的Action } </script> <h2> 用户列表</h2> @using (Html.BeginForm("Deletes", "UserInfo", FormMethod.Post)) { <p> <a href="/UserInfo/Create">添加用户</a> <a href="/UserInfo/Search">用户查询</a> <input type="button" onclick="delIds();" value="批量删除" /> </p> <p> 用户名:<input type="text" name="name" value="@ViewBag.Name" /> 电 话:<input type="text" name="phone" value="@ViewBag.Phone" /> <input type="button" value="查询" onclick="search();"/> </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> }
没有新的知识,大家应都可以看懂,现在我们重新编译项目,运行,点击用户管理
随意输入一段字符串验证一下查询结果:
测试成功。
如果想详细了解命名参数和可选参数,请参考此处http://www.cnblogs.com/weiming/archive/2011/12/28/2304937.html
2.建立商品实体和添加商品实体的数据库操作;
截至目前,我们已经完成了用户的绝大多数操作,现在我们来开始进行商品管理的编码,首先,右键Models,添加商品管理的实体模型,命名为Product
然后再同样添加商品类别模型实体,命名为Category
修改Categeory代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; using System.ComponentModel; namespace MyShopTest.Models { public class Categeory { public int Id { get; set; } [DisplayName("类别名称")] [Required(ErrorMessage="请输入名称!")] public string Name { get; set; } //该类别所有的产品 public virtual List<Product> Products { get; set; } } }
修改Product代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; namespace MyShopTest.Models { public class Product { public int Id { get; set; } [Display(Name = "商品名称")] [Required(ErrorMessage = "必须输入名称!")] public string Title { get; set; } [Display(Name = "价格")] [Required(ErrorMessage = "必须输入价格!")] [Range(0.01, 1000, ErrorMessage = "输入的价格必须是0.01到1000")] public double Price { get; set; } [Display(Name = "图片")] public string ImgUrl { get; set; } //类别Id public int CategoryId { get; set; } private DateTime addTime; [Display(Name = "添加时间")] [Required()] public DateTime AddTime { get { if (addTime == null) { return DateTime.Now; } return addTime; } set { addTime = value; } } //类别实体(主外键) public virtual Categeory Category { get; set; } } }
修改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; } public DbSet<Categeory> Category { get; set; } public DbSet<Product> Products { get; set; } } }
修改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)); //类别数据 var categorys = new List<Categeory>(); categorys.Add(new Categeory { Name = "食品" }); categorys.Add(new Categeory { Name = "宠物" }); //产品数据 var products = new List<Product> { new Product{ AddTime=DateTime.Now,Category=categorys.Find(delegate(Categeory c){return c.Name=="食品"; }), ImgUrl="0", Price=8.8, Title="冰淇淋"}, new Product{ AddTime=DateTime.Now,Category=categorys.Single(c=>c.Name=="宠物"), ImgUrl="0", Price=88.8, Title="大熊猫"}, new Product{ AddTime=DateTime.Now,Category=categorys.Single(c=>c.Name=="宠物"), ImgUrl="0", Price=88.8, Title="北极熊"} }; products.ForEach(p => context.Products.Add(p)); } catch (DbEntityValidationException dbEx) { string a = dbEx.Message; } } } }
重新编译项目,运行,打开数据库,我们来看一下创建的数据库表
如果没有出现新增的表,请点击一下用户管理,然后刷新数据库再试。
现在我们来解析一下代码中大家可能不理解的地方:
Category.cs
public virtual List<Product> Products { get; set; }
这段代码的作用是我们再用EF和LinQ结合查询时,可以在查询该类别时获取该类别所有的产品,具体的代码我们后续会见到,virtual的意思是延缓加载,以避免数据量很多的时候耽误我们只想要的类别名称等其他字段的查询效率;
Product.cs
//类别Id public int CategoryId { get; set; }
//类别实体(主外键) public virtual Categeory Category { get; set; }
这两端代码的作用是告诉EF,Product有一个字段和Category 是主外键关系,而主外键关系的字段是CategoryId ,假如我们不指定CategoryId,也就是说 Product类只有public virtual Categeory Category { get; set; }来表明主外键关系这也是可以的,系统会自动生成Category_Id这样的字段来表名主外键关系。
InitData.cs
//类别数据 var categorys = new List<Categeory>(); categorys.Add(new Categeory { Name = "食品" }); categorys.Add(new Categeory { Name = "宠物" }); //产品数据 var products = new List<Product> { new Product{ AddTime=DateTime.Now,Category=categorys.Find(delegate(Categeory c){return c.Name=="食品"; }), ImgUrl="0", Price=8.8, Title="冰淇淋"}, new Product{ AddTime=DateTime.Now,Category=categorys.Single(c=>c.Name=="宠物"), ImgUrl="0", Price=88.8, Title="大熊猫"}, new Product{ AddTime=DateTime.Now,Category=categorys.Single(c=>c.Name=="宠物"), ImgUrl="0", Price=88.8, Title="北极熊"} }; products.ForEach(p => context.Products.Add(p));
这里的delegate(Categeory c){return c.Name=="食品"; }是利用委托查找满足指定条件的类别对象赋给Product Category,categorys.Single(c=>c.Name=="宠物")也是这样的作用,只不过故意写出两种,让大家了解一下,此处不需要为CategoryId赋值,系统会自动寻找赋值。另外,细心的人或许注意到了,我们虽然构造了类别的初始数据,但是没有添加它们进入数据库,但是我们打开数据库会发现它已经加入进去了,这也是主外键关系的一个好处,产品初始化时,系统会自动找到上方类别集合的数据,先行加入数据库,然后将对应Id赋值CategoryId。到此出产品的相关模型已经建立完成了,下一章我们会对他们进行相关操作。