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 AddCreateDateToSysUserupdate-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, 就不举展开讲了,很容易可以看懂,大家可以自己尝试。

[Display(Name="用户名")]

[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>

Email

</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

例子:

[Display(Name="用户名")]

 

二、掌握加载多表数据两种应用场景

 

好了,到目前为止,使用MVC+EF开发的基本知识差不多都齐全了。

在下一课,也就是我们第一阶段的最后一课:MVC5+EF6入门完整教程十,我们将再讲解下数据更新的部分,以及如何使用原生SQL,如何调用存储过程等。

祝大家新年快乐,学习进步!