MVC5+EF6 入門完整教程九
前一陣子臨時有事,這篇文章發布間隔比較長,我們先回顧下之前的內容,每篇文章用一句話總結重點。
文章一 MVC核心概念簡介,一個基本MVC項目結構
文章二 通過開發一個最基本的登錄界面,介紹了如何從Controller中獲取表單數據
文章三 EF的整個開發過程
文章四 EF基本的CRUD和常用的HtmlHelper
文章五 使用布局頁(模板頁)改造UI
文章六 分部視圖(Partial View)
文章七 排序過濾分頁
文章八 不丟失數據進行數據庫結構升級
以上如果有不清楚的可以再回去看一下。
文章提綱
- 理論基礎
- 應用場景
- 總結
理論基礎
基於前面的文章,本次我們更近一步,進行更加深入的講解, 首先介紹下Attribute配置Data Model.
使用Attribute配置Data Model, 可以指定formatting, validation, database mapping rules
約定:下圖中三種情況一般資料都翻譯成"屬性",為了區分,我們用下圖中的表述方式。
接下來,我們先對常用的attribute進行舉例說明。
一、常用Attribute
DataType,DisplayFormate
首先打開ModelsàSysUser.cs
添加 public DateTime CreateDate { get; set; }
增加完之後及時使用Code First Migrations 方式更新數據庫。(不然運行時會報contex和database不一致的錯誤)
Note
註意把Migrations\ Configuration.cs中Seed方法中內容註釋掉,因為
模型變了,插入示例數據時會報一個錯誤。
運行 add-migration AddCreateDateToSysUser,update-database即可更新
更新後隨便在數據庫中插入兩個日期值。
Code First Migrations 方式更新數據庫詳細做法參加上篇文章。
接著修改Views\Account\Index.cshtml,把創建日期顯示出來,如下方框處。
大家註意到,默認情況下會顯示出時間,我們只需要顯示到日就可以了。
下面我們就把CreateDate調整到我們需要的格式。
打開 Models\SysUser.cs, 做如下修改。
DataType 屬性用來指定更加具體的數據類型,DataType枚舉值提供了一些常見的類型,比如Date,Time,EmailAddress等。
但是DataType不能指定數據類型的顯示格式,例如日期要什麽格式顯示。
默認情況下顯示格式會根據電腦的設定顯示。
這個時候就需要配合使用DisplayFormate屬性來指定格式。
[DisplayFormat(DataFormatString="{0:yyyy-MM-dd}",ApplyFormatInEditMode=true)]
StringLength
你可以指定數據驗證規則以及出錯信息。
StringLength屬性設置了數據庫中存儲字段的最大長度,為程序提供客戶端和服務器端的驗證。同樣用這個屬性也可以指定最小長度,不過不影響數據庫的結構。
同樣更新下數據庫
add-migration MaxLengthOnNames
update-database
先去數據庫看下,可以看到已經有長度限制了。
我們再修改下Create方法,測試下驗證。
之前我們的模型太簡陋了,為了看到效果,再做兩處修改。
Views\Account\Create.cshtml增加一個Helper:ValidationMessageFor用來顯示驗證信息
Controllers\AccountController.cs增加一個判斷條件ModelState.IsValid,不然會出錯。
運行可以看到如下效果:
Column
這個屬性也非常實用。
有時會有這麽一種情況,我們Model中的字段和數據庫中表的字段要用不同的命名。例如我們Model中命名為UserName,數據庫表中命名為LoginName.
這個時候就用到Column了。
同樣運行更新指令。
add-migration ColumnLoginName
update-database
打開數據庫可以看到UserName已經變成LoginName了。
下面再列出其他常用的attribute, 就不舉展開講了,很容易可以看懂,大家可以自己嘗試。
)]
[StringLength(10,MinimumLength=1,ErrorMessage="名字在1和10個字之間")]
Note
1.可以將多個屬性寫在一塊用逗號隔開,例如
[Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
2.對某一些類型來說不需要使用Required, 例如DateTime, int,double,float,因為這些值類型不能被賦予空值,因此他們天生就具有Required的特性。
[Column(TypeName="money")]
public
decimal
Budget { get; set; }
之前用Column可以改變數據庫中列名。
指定Column的TypeName可以改變SQL data type,這個例子中就是知道使用SQL Server的money類型。
Column mapping一般來說不需要,因為EF通常會基於你為property定義的CLR類型選擇合適的SQL Server data type.
The CLR decimal type maps to a SQL Server decimal type.
詳細對應表:https://msdn.microsoft.com/en-us/library/bb896344.aspx
二、Lazy, Eager, and Explicit Loading of Related Data
前面文章中我們介紹過顯示關聯表數據的方法。
第四篇文章介紹過通過navigation 屬性顯示關聯表數據。
本篇文章就系統的講解下多表關聯數據顯示的問題。
有三種方式EF可以加載關聯數據到一個實體的navigation屬性中,下面我就直接用MSDN上的截圖來說明。
Lazy loading
第一次讀取entity的時候不會加載。
當需要讀取navigation property的時候,相關的數據將會被自動讀取。
這種情況會導致多次查詢數據庫。
Eager loading
當讀取entity的時候,相關數據會被一起讀取。
一般來說這種方式會產生一個join query來獲取所有需要的數據。
通過Include方法來指定eager loading.
Explicit loading
和lazy loading類似,除了需要在代碼中明確指定需要獲取的關聯數據。
在讀取navigation property時explicit loading 不會自動發生,你需要手動加載相關數據。
通過獲取object state manager entry for entity,調用Collection.Load method for collections或者Reference.Load method for properties that hold a single entity.
一般來說,只有在關閉lazying loading的時候才會使用explicit loading
lazy loading 和 explicit loading都不立即獲取property values,它們也被稱作deferred loading.
Disable lazy loading before serialization
disable lazy loading的兩種方式:
1.對特定的navigation properties來說,省略property的virtual關鍵字就可以了
2.對所有navigation properties來說, 在context類中,構造函數中設置LazyingLoadingEnabled 為false即可。
this.Configuration.LazyLoadingEnabled = false;
應用場景
場景一:多對一關系,顯示用戶及相應的部門(* to 0 or 1)
新建一個entity: SysDepartment
我們約定,某個用戶只能歸屬於0個或1個部門。
即用戶和部門的關系為(* to 0 or 1)
原來的SysUser中添加一個如下兩個property
使用 code first migrations的方式更新下數據庫。可以看到新的表結構已經生成了。
去數據庫中SysDepartment添加兩筆資料。
去數據庫中SysUser修改用戶對應的department
先看下原來的Views\Account\Index.cshtml
我們原來是顯示SysUser主表內容,當點擊Details時通過navigation property實現SysUseràSysUserRoleàSysRole多表間查詢。
現在我們增加一列Department, 讓這個表格能直接顯示SysUser主表及相應的Department內容。
我們使用Eager Loading的方式將Department的內容也加載進去,打開Controllers\AccountController.cs, 在index修改一處地方:
修改對應的View
運行,可以看到Department中的內容已經被我們加載進來了。
這個就是第一種場景,多對一的情況。
下面我們再來看多對多的情況。
場景二:多對多關系
多對多關系可以拆解成一對多的關系,例如用戶和角色(* to *)可拆解成:
顯示用戶及相應的角色(1 to *)
顯示角色及相應的用戶(1 to *)
為了演示這個場景,我們新建一個ViewModel,將需要顯示的表都放進去。
創建相應的Controller和View
因為前面的文章已經將基本的用法都講過了,我這裏就直接貼出代碼以及最終的展示結果,如果有不理解的部分再給我留言。
Controllers\UserRoleController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCDemo.ViewModels;
using MVCDemo.DAL;
using MVCDemo.Models;
using System.Data.Entity;
namespace MVCDemo.Controllers
{
public class UserRoleController : Controller
{
private AccountContext db = new AccountContext();
//
// GET: /UserRole/
public ActionResult Index(int? id)
{
var viewModel = new UserRoleIndexData();
viewModel.SysUsers = db.SysUsers
.Include(u=>u.SysDepartment)
.Include(u => u.SysUserRoles.Select(ur => ur.SysRole))
.OrderBy(u => u.UserName);
if (id!=null)
{
ViewBag.UserID = id.Value;
viewModel.SysUserRoles = viewModel.SysUsers.Where(u => u.ID == id.Value).Single().SysUserRoles;
viewModel.SysRoles = (viewModel.SysUserRoles.Where(
ur => ur.SysUserID == id.Value)).Select(ur=>ur.SysRole);
}
return View(viewModel);
}
}
}
Views\UserRole\Index.cshtml
@model MVCDemo.ViewModels.UserRoleIndexData
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>UserRoles</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table table-striped">
<tr>
<th>
UserName
</th>
<th>
</th>
<th>
CreateDate
</th>
<th>
Department
</th>
<th>
Roles
</th>
<th></th>
</tr>
@foreach (var item in Model.SysUsers)
{
string selectedRow = "";
if (item.ID==ViewBag.UserID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.CreateDate)
</td>
<td>
@if (item.SysDepartment != null)
{
@item.SysDepartment.DepartmentName
}
</td>
<td>
@{
foreach (var userRole in item.SysUserRoles)
{
@userRole.SysRole.RoleName <br />
}
}
</td>
<td>
@Html.ActionLink("Select", "Index", new { id = item.ID })
</td>
</tr>
}
</table>
@if (Model.SysRoles != null)
{
<h3>Related Roles</h3>
<table class="table table-striped">
<tr>
<th>RoleName</th>
<th>RoleDesc</th>
</tr>
@foreach (var item in Model.SysRoles)
{
<tr>
<td>
@item.RoleName
</td>
<td>
@item.RoleDesc
</td>
</tr>
}
</table>
}
最終展示結果:
總結
一、掌握常用attribute
DataType
例子:[DataType(DataType.Date)]
DisplayFormat
例子:
[DisplayFormat(DataFormatString="{0:yyyy-MM-dd}",ApplyFormatInEditMode=true)]
[DisplayFormat(NullDisplayText = "No grade")]
StringLength
例子:
[StringLength(10,MinimumLength=1,ErrorMessage="名字在1和10個字之間")]
Column
例子:
[Column("FirstName")]
[Column(TypeName="money")]
Display
例子:
)]
二、掌握加載多表數據兩種應用場景
好了,到目前為止,使用MVC+EF開發的基本知識差不多都齊全了。
在下一課,也就是我們第一階段的最後一課:MVC5+EF6入門完整教程十,我們將再講解下數據更新的部分,以及如何使用原生SQL,如何調用存儲過程等。
祝大家新年快樂,學習進步!
MVC5+EF6 入門完整教程九