• MVC5+EF6 入门完整教程9:多表数据加载


    1 MVC核心概念简介,一个基本MVC项目结构

    2 通过开发一个最基本的登录界面,介绍了如何从Controller中获取表单数据

    3 EF的整个开发过程

    4 EF基本的CRUD和常用的HtmlHelper

    5 使用布局页(模板页)改造UI

    6 分部视图(Partial View)

    7 排序过滤分页

    8 不丢失数据进行数据库结构升级

    一、Attribute配置Data Model.

    使用Attribute配置Data Model, 可以指定formatting, validation, database mapping rules

    约定:下图中三种情况一般资料都翻译成"属性",为了区分,我们用下图中的表述方式。

    接下来,我们先对常用的attribute进行举例说明。

    常用Attribute

     DataType,DisplayFormate

    在模型User.cs 添加 public DateTime CreateDate { get; set; }

    增加完之后及时使用Code First Migrations 方式更新数据库。(不然运行时会报contex和database不一致的错误)

    Note

    注意把Migrations Configuration.cs中Seed方法中内容注释掉,因为

    模型变了,插入示例数据时会报一个错误。

    运行 add-migration AddCreateDateToSysUser,update-database即可更新

    更新后随便在数据库中插入两个日期值。

    接着修改ViewsAccountIndex.cshtml,把创建日期显示出来,如下方框处。

    大家注意到,默认情况下会显示出时间,我们只需要显示到日就可以了。

    下面我们就把CreateDate调整到我们需要的格式。

    打开 ModelsSysUser.cs, 做如下修改。

    DataType 属性用来指定更加具体的数据类型,DataType枚举值提供了一些常见的类型,比如Date,Time,EmailAddress等。

    但是DataType不能指定数据类型的显示格式,例如日期要什么格式显示。

    默认情况下显示格式会根据电脑的设定显示。

    这个时候就需要配合使用 DisplayFormate属性来指定格式。

    [DisplayFormat(DataFormatString="{0:yyyy-MM-dd}",ApplyFormatInEditMode=true)]

    StringLength

    你可以指定数据验证规则以及出错信息。

    StringLength属性设置了数据库中存储字段的最大长度,为程序提供客户端和服务器端的验证。同样用这个属性也可以指定最小长度,不过不影响数据库的结构。

    同样更新下数据库

    add-migration MaxLengthOnNames
    update-database

    先去数据库看下,可以看到已经有长度限制了。

    我们再修改下Create方法,测试下验证。

    之前我们的模型太简陋了,为了看到效果,再做两处修改。

    ViewsAccountCreate.cshtml增加一个Helper:ValidationMessageFor用来显示验证信息

    ControllersAccountController.cs增加一个判断条件ModelState.IsValid,不然会出错。

            [HttpPost]
            public ActionResult Create(User user)
            {
                if(ModelState.IsValid)  // 验证网页输入的数据的合法性
                {
                    user.CreateDate = DateTime.Now;
                    db.Users.Add(user);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                return View();
            }

    运行可以看到如下效果:

    Column 改变数据库中列名

     这个属性也非常实用。

    有时会有这么一种情况,我们Model中的字段和数据库中表的字段要用不同的命名。例如我们Model中命名为UserName,数据库表中命名为LoginName或 用户名称

    这个时候就用到Column了。

    同样运行更新指令。

    add-migration ColumnLoginName

    update-database

    打开数据库可以看到UserName已经变成LoginName了。

    [StringLength(10,MinimumLength=1,ErrorMessage="名字在1和10个字之间")]

    多个属性写一块用逗号隔开,例如 [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]

    某些类型不需要使用Required, 例如DateTime, int,double,float,因为这些值类型不能被赋予空值,因此他们天生就具有Required的特性。

    Column 改变数据库中列的类型

    指定Column的TypeName可以改变SQL data type,这个例子中就是知道使用SQL Server的money类型。

    [Column(TypeName="money")]
    public decimal Budget { get; set; }

    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

    二、EF6 关联数据加载分为以下三种    https://docs.microsoft.com/zh-cn/ef/ef6/querying/related-data

      Lazy loading

      这种加载方式在于需要用到这个导航属性数据的时候,才会去数据库取数据,如下图,循环中,每一次都去数据库取一次数据:

      

      Eager loading

      这种加载方式则是先定义好哪个导航属性数据需要一起加载(通过是.Inclue),然后在加载主数据的时候,一并把导航数据全部加载,如下图:

      

      Explicit loading

      这种加载就是需要明确用代码来定义去数据库取数据(通过Collection.Load()或者Reference.Load()),一般是用在Lazy Loading 被关闭的情况下;如:

      

    性能考虑

      显而易见,Lazy Loading和Eager Loading 各有优势劣势;

      Lazy Loading可以在需要的时候才取数据,那么不需要的时候就节约了资源;但需要的时候也对数据库产生了很大压力;

      Eager Loading可以在需要的时候一次性取到全部数据,对于数据库压力来说会好很多;

      所以,根据自己需要选择哪一种咯。。。

    序列化之前关闭Lazy Loading

      如果要序列化一个实体,会出现导航属性一层一层展下去,出现循环等等问题,所以在WEB API或者特定应用场景下需要序列化实例的时候,需要做些特殊处理;

           准备深入学习此部分;计划后续再补充这部分

    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: Department

    我们约定,某个用户只能归属于0个或1个部门,即用户和部门的关系为(* to 0 or 1)

        public class Department
        {
            public int ID { get; set; }
            public string DepartmentName { get; set; }
            public string DepartmentDesc { get; set; }
            public virtual ICollection<User> Users { get; set; }
        }

    原来User中添加一个如下两个property

    使用 code first migrations的方式更新下数据库。可以看到新的表结构已经生成了。

    去数据库中SysDepartment添加两笔资料。

    去数据库中SysUser修改用户对应的departmen

    先看下原来的ViewsAccountIndex.cshtml

    我们原来是显示User主表内容,当点击Details时通过navigation property实现User 、UserRole 、Role多表间查询。
    现在我们增加一列Department, 让这个表格能直接显示User主表及相应的Department内容。
    我们使用Eager Loading的方式将Department的内容也加载进去,打开ControllersAccountController.cs, 在index修改一处地方:

    修改对应的View

    运行结果:

    场景二:多对多关系  用户 --- 角色

     多对多关系可以拆解成一对多的关系,例如用户和角色(* to *)可拆解成:

    显示用户及相应的角色(1 to *)

    显示角色及相应的用户(1 to *)

    为了演示这个场景,我们新建一个ViewModels ,将需要显示的表都放进去。

    建立控制器:UserRoleController

    using MVCDemo.DAL;
    using MVCDemo.ViewModels;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace MVCDemo.Controllers
    {
        public class UserRoleController : Controller
        {
            private DemoContext db = new DemoContext();
            //
            // GET: /UserRole/
    
            public ActionResult Index(int? id)
            {
                var viewModel = new UserAndUserRole();
                viewModel.Users = db.Users
                    .Include("Department")
                    .Include("UserRoles")
                .OrderBy(u => u.UserName);
    
                if (id != null)
                {
                    ViewBag.UserID = id.Value;
                    viewModel.UserRoles = viewModel.Users.Where(u => u.ID == id.Value).Single().UserRoles;
                    viewModel.Roles = (viewModel.UserRoles.Where(ur => ur.UserID == id.Value)).Select(ur => ur.Role);
                }
                return View(viewModel);
            }
        }
    }

    运行时出现错误:

    {"已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。"}

    问题解决见:

    https://www.cnblogs.com/maxao/archive/2011/03/18/1988168.html

    https://blog.csdn.net/abrahamchen/article/details/86647978

    https://www.cnblogs.com/dongshuangjie/p/4832100.html

    修改如下:

        public class UserRoleController : Controller
        {
            private DemoContext db = new DemoContext();
            //
            // GET: /UserRole/
    
            public ActionResult Index(int? id)
            {
                var viewModel = new UserAndUserRole();
                viewModel.Users = db.Users
                    .Include("Department")
                    .Include("UserRoles")
                .OrderBy(u => u.UserName).ToList();
    
                if (id != null)
                {
                    ViewBag.UserID = id.Value;
                    viewModel.UserRoles = viewModel.Users.Where(u => u.ID == id.Value).Single().UserRoles;
                    viewModel.Roles = (viewModel.UserRoles.Where(ur => ur.UserID == id.Value)).Select(ur => ur.Role);
                }
                return View(viewModel);
            }
        }

    最终展示结果:

    总结

    一、掌握常用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")]

  • 相关阅读:
    Linux之vmware安装
    中秋之美
    青春无悔
    MSP430常见问题之指令系统类
    MSP430常见问题之LCD 显示驱动类
    MSP430常见问题之FLASH存储类
    MSP430常见问题之看门狗及定时器类
    MSP430常见问题之电源类
    MSP430常见问题之通信类
    MSP430常见问题之AD转换类
  • 原文地址:https://www.cnblogs.com/wfy680/p/14258694.html
Copyright © 2020-2023  润新知