MVC3学习第十一章 葵花点穴手之隔空点穴----MVC3下利用EF和LINQ进行简单的多表联查、排序以及在Razor视图中调用自定义类
本章学习内容
1.MVC3下利用主外键关系使用LINQ进行简单的多表联查和排序
2.在Razor视图中调用自定义类(包含创建自己的HtmlHelper)
1.MVC3下利用主外键关系使用LINQ进行简单的多表联查和排序
在上一章中我们实现了商品标题的模糊查询,现在我们还想这个文本框也能实现类别的模糊查询,我们稍微改动一下ProductController.cs的查询方法,这次就不贴出完整代码了,自己修改替换即可:
//商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; }
我们测试一下效果:
效果是可以达到的,如果我说这就是EF结合Linq实现的强大的多表联查的提现之一,你或许会觉得过于简单,事实上这也确实是一个多表联查,当然或许与各位想的多表联查相比简单了许多,没关系,见微知著,道理都是一样的,因为我们这个系统本身就是练习的简单项目,我也实在找不到复杂的关系来实现一个复杂的多表联查来展现出来。当然为了开阔一下思维,我们还是来说一下简单的基于主外键关系的多表联查:
再次修改Search方法,代码如下:
//商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Include(p=>p.Category).Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; }
我们使用了一个Include扩展方法;
编译项目,测试产品查询结果:
依然正确,我们再次修改Search方法,代码如下:
//商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Include("Category").Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; }
继续测试结果:
好了,现在我们来解析一下上方三种情况:
var products = db.Products.Where(p => p.Id > 0);
var products = db.Products.Include(p=>p.Category).Where(p => p.Id > 0);
var products = db.Products.Include("Category").Where(p => p.Id > 0);
三次代码不一样,结果都一样。这里先说一下Include,这是LINQ中(转到定义却是在EF的dll下,让我有点模糊)的一个方法,它在表间有关系时可以通过我们预定义好的实体关系可以帮我们实现关联表间的数据联合查询。第二和第三中写法其实是作用是一样的,只不过一个是用Lumda表达式来指明要包含的数据,一个用的字符串名称来指定,或许第二种和第三种写法我们更好理解一些,但是第一种写法里并没有包含Include,为什么也能查到呢?
我们再来看一下Produc实体模型的代码:
这里表明了Product的有一个属性是Category,好吧,这不是关键,我们之前说过virtual是延迟加载的,主要是为了效率,现在我们去掉virtual;
//类别实体(主外键) public Categeory Category { get; set; }
将Search方法改为:
//商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; }
编译测试,
类别数据没有了,测试其他两种写法,都没有问题,可以正常显示类别,由此可以得出virtual的作用或许并不只是延迟加载,它可以在我们不写Includ的情况下自动加载与我们要查询的数据相关联的一些数据。
好了,请把代码恢复到最初,现在我们来实现一个排序:
//商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Include(p => p.Category).Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 products = products.OrderBy(p => p.AddTime);//根据时间升序排列 //products = products.OrderByDescending(p => p.Id);//根据Id降序排列 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; }
注释都已说明了,我们就不做解析了。
现在我们再来实现一个功能,在类别中可以点击查看该类别的所有商品:
修改ProductController.cs完整代码如下:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; using MyShopTest.Models; using System.Data.Entity.Validation; namespace MyShopTest.Controllers { public class ProductController : Controller { private MyShopDataEntities db = new MyShopDataEntities(); //列表 public ViewResult Index(string title = "") { var products = Search(title); ViewBag.Name = title; return View(products.ToList()); } //商品查询 public IEnumerable<Product> Search(string titles) { var products = db.Products.Include(p => p.Category).Where(p => p.Id > 0);//此处的查询没有任何意义,只是返回所有数据 products = products.OrderBy(p => p.AddTime);//根据时间升序排列 //products = products.OrderByDescending(p => p.Id);//根据Id降序排列 if (!string.IsNullOrEmpty(titles)) { products = products.Where(p => p.Title.IndexOf(titles) > -1 || p.Category.Name.IndexOf(titles) > -1); } return products; } //商品展示 public ViewResult Show(int cId = 0) { var cat = db.Category.Single(c => c.Id == cId); //var cat = db.Category.Include(c => c.Products).Single(c => c.Id == cId);//两者作用一样,可以直接获取不用include,但是经测试发现没有给对应主外键的实体填写virtual修饰符时,不写Include得不到关联实体的值 return View(cat); } //详细 public ViewResult Details(int id) { Product product = db.Products.Find(id); return View(product); } //添加 public ActionResult Create() { ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name"); return View(); } //添加处理 [HttpPost] public ActionResult Create(Product product)//此时已经自动匹配好了 { try { //图片 var file = Request.Files[0]; var img = new CommonHelper().fileSaveAs(file, 0, 2); if (img.IndexOf("UpFile") > -1) { product.ImgUrl = img; } else { ModelState.AddModelError("error", img);//添加了key的错误信息,前台使用Html.ValidationMessage(key)的形式访问,如果key为空,用Html.ValidationSummary(true)显示 ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); return View(product); } db.Products.Add(product); db.SaveChanges(); return RedirectToAction("Index"); } catch (DbEntityValidationException ex) { string mes = ex.Message; ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); return View(product); } } //编辑 public ActionResult Edit(int id) { Product product = db.Products.Find(id); ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); return View(product); } //编辑处理 [HttpPost] public ActionResult Edit(int id, FormCollection collection) { var product = db.Products.Find(id); //ModelState.IsValid,此处相当于后台验证,防止前台验证因为各种情况被跳过或失效 try { bool updateRes = TryUpdateModel(product);//如果字段符合则会赋值,否则保持原有 //db.Entry(product).State = EntityState.Modified; if (updateRes) { //图片 var file = Request.Files[0]; var img = new CommonHelper().fileSaveAs(file, 0, 2); if (img.IndexOf("UpFile") > -1) { product.ImgUrl = img; } //else //{ // ModelState.AddModelError("", img); // ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); // return View(product); //} db.SaveChanges(); return RedirectToAction("Index"); } else { ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); ModelState.AddModelError("", "更新失败!"); return View(product); } } catch (Exception) { ViewBag.CategoryId = new SelectList(db.Category, "Id", "Name", product.CategoryId); return View(product); } } //删除处理 [HttpPost] public ActionResult Delete(FormCollection coll) { string ids = coll["ckSelect"]; foreach (var item in ids.Split(','))//循环每一项Id { if (item != "false")//筛选掉自动生成的checkbox初始值 { var user = db.Products.Find(Convert.ToInt32(item)); db.Products.Remove(user); } } db.SaveChanges(); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
修改Category/Index.cshtml完整代码如下:
@model IEnumerable<MyShopTest.Models.Categeory> @{ ViewBag.Title = "类别管理"; } <script src="../../Scripts/jquery-1.5.1-vsdoc.js" type="text/javascript"></script> <h2> 类别管理</h2> <p> @Html.ActionLink("添加类别", "Create") </p> <table> <tr> <th> 类别名称 </th> <th> </th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.ActionLink("编辑", "Edit", new { id = item.Id }) | @Html.ActionLink("删除", "Delete", new { id = item.Id }) | @Html.ActionLink("该类商品", "Show", "Product", new { cId = item.Id }, null) | </td> </tr> } </table>
增加Produc/Show视图,完整代码如下:
@model MyShopTest.Models.Categeory @{ ViewBag.Title = "商品展示"; } <h2>类别展示</h2> <p> 当前类别:@Model.Name </p> <table> <tr> <th> 标题 </th> <th> 价格 </th> <th> 类别 </th> <th> 上架时间 </th> </tr> @foreach (var item in Model.Products) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Model.Name </td> <td> @Html.DisplayFor(modelItem => item.AddTime) </td> </tr> } </table>
重新编译项目,运行:
点击该类商品:
还是为了展示一下EF和LINQ强大的查询功能,代码不做解释。
2.在Razor视图中调用自定义类(包含创建自己的HtmlHelper)
在之前我们完成了商品和用户的列表功能,假如当一个用户名或者商品名过于长的时候,可能会导致撑开表格的样式,不太美观,也可能有些诸如数字或者日期的字符串需要做一些处理,更或者我们的一些数据需要做一些逻辑处理才能显示出来(大多数这种情况应避免在视图层出现,毕竟这有违View层的初衷意义),这都需要我们调用一些我们自己的类来处理。
右键Models文件夹,添加MyHelper类,修改完整代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MyShopTest.Models { public static class MyHelper { /// <summary> /// 自定义方法截取字符串 /// </summary> /// <param name="content"></param> /// <param name="length"></param> /// <returns></returns> public static string CutString(string content, int length) { if (content.Length>length) { return content.Substring(0,length)+"..."; } else { return content; } } /// <summary> /// 自定义扩展helper截取字符串 /// </summary> /// <param name="helper"></param> /// <param name="content"></param> /// <param name="length"></param> /// <returns></returns> public static string CutString(this System.Web.Mvc.HtmlHelper helper, string content, int length) { if (content.Length > length) { return content.Substring(0, length) + "..."; } else { return content; } } } }
代码都很简单,唯独this System.Web.Mvc.HtmlHelper helper或许不太理解,这一句的意思是表名这个是helper扩展方法,可以通过@Html点出来。
我们修改Product/Index.cshtml,完整代码如下:
@model IEnumerable<MyShopTest.Models.Product> @{ ViewBag.Title = "商品列表"; } @using MyShopTest.Models <script src="../../js/Helper.js" type="text/javascript"></script> <script type="text/jscript"> function search() { $("form").attr("action", "/Product/Index").submit(); //更改表单提交的Action } </script> <h2> 商品列表</h2> @using (Html.BeginForm("Delete", "Product", FormMethod.Post)) { <p> @Html.ActionLink("添加", "Create") <input type="button" onclick="delIds();" value="批量删除" /> </p> <p> 名称或类别:<input type="text" name="title" value="@ViewBag.Name" /> <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> @Html.CheckBox("ckSelect", false, new { value = item.Id }) </td> <td> @Html.CutString(item.Title, 3)<br /> @MyHelper.CutString(item.Title, 3) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Html.DisplayFor(modelItem => item.Category.Name) </td> <td> @Html.DisplayFor(modelItem => item.AddTime) </td> <td> @Html.ActionLink("编辑", "Edit", new { id = item.Id }) | @Html.ActionLink("查看", "Details", new { id = item.Id }, new { target = "_blank" }) | </td> </tr> } </table> }
修改不多:
@using MyShopTest.Models
引用命名空间:
@Html.CutString(item.Title, 3)<br /> @MyHelper.CutString(item.Title, 3)
调用截取字符串方法,一个是使用helper扩展,一个是使用自定义。