博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如鹏网学习笔记(十五)ASP.NET MVC核心基础笔记
阅读量:4554 次
发布时间:2019-06-08

本文共 56637 字,大约阅读时间需要 188 分钟。

一、ASP.Net MVC简介

  1,什么是ASP.NET MVC?
    HttpHandler是ASP.net的底层机制,如果直接使用HttpHandler进行开发难度比较大、工作量大。因此提供了ASP.Net MVC、
    ASP.Net WebForm等高级封装的框架,简化开发,他们的底层仍然是HttpHandler、HttpRequest等
    例如:ASP.NET MVC的核心类仍然是实现了IHttpHandler接口的MVCHandler
  2,ASP.NET WebForm和ASP.NET MVC的关系?
    两者都是对HttpHandler的封装框架,ASP.NET MVC的思想,更适合现代项目的开发,因此会逐步取代WebForm
  3,为什么ASP.NET MVC更好
    程序员有更强的掌控力,不会产生垃圾代码;程序员能够更清晰的控制运行过程,因此更安全、性能和架构等更清晰。
    入门“难”,深入“相对比较简单”
  4,什么是MVC模式
    模型(Model)、视图(View)、控制器(Controller)
    Model负责在View和控制器之间进行数据的传递(用户输入的内容封装成Model对象,发送给Controller);
    要显示的数据由Controller放到Model中,然后扔给View去显示。
    Controller不直接和View交互
  5,ASP.Net MVC与“三层架构”没有任何关系。
    唯一的“关系”:三层中的UI层可以用ASP.Net MVC来实现
  6,“约定大于配置”:
二、ASP.Net MVC起步
  1,项目的创建
    新建项目——C#——Web——ASP.NET Web应用程序(不要勾选“将Application Insights添加到项目”)——确定;
    选中“Empty”——勾选MVC(不要勾选Host in the cloud)——确定
  2,控制器的建立和视图的建立
    在Controller文件夹下右键——添加——控制器——选择“MVC5控制器-空”,
    注意:类的名字以Controller结尾,会自动在View文件夹下创建一个对应名字的文件夹(没有就手动创建文件夹)

    在View/文件夹名字 下创建视图Index(和XXXController的Index方法一致)

    注意:添加视图时,模板选择Empty,不要勾选创建为分部视图和使用布局页
  3,新建一个用来收集用户参数的类
    IndexReqModel(类名无所谓,可以随便起)包含Num1、Num2两个属性(只要不重名,大小写都可以)
    然后声明一个IndexRespModel类用来给view传递数据显示,有Num1、Num2、Result。
    也可以同一个类实现,但是这样写看起来比较清晰

    代码:

      public class TestControler:Controller      {        public ActionResult Index(IndexReqModel model)        {          IndexReqModel resq = new IndexReqModel();          resq.num1 = model.Num1;          resq.num2 = model.Num2;          resq.result = model.Num1 + model.Num2;          return View(resq);        }      }

 

  4,Index.cshtml的代码

    @model Test1.Models.IndexReqModel                      
        Index                    
          
+
=@Model.Result        
          

 

  5,在浏览器访问:http://localhost:56919/Test/Index?num1=1&num2=2

  6,执行过程、数据流动分析:
    当用户访问“Test/Index?num1=1&num2=2”的时候,会找到Controller下的TestController的Index方法去执行,
    把请求参数按照名字填充到Index方法的参数对象中(MVC引擎负责创建对象,给数据赋值,并且进行类型的转换),
    return View(resq)就会找到Views下的和自己的“类名、方法名”相对应的Index.cshtml,然后把数据resp给到Index.cshtml去显示。
    注意:
      1,@model Test1.Models.IndexReqModel //这里的model要小写开头,表示传递过来的数据是IndexReqModel类型的
      2,@Model指传递过来的对象 //这里的Model要大写开头
      3,cshtml模板就是简化HTML的拼接的模板,最终还是生成html给浏览器显示,不能直接访问cshtml文件
三、Razor语法

  1,语法简单:

    @启动的区域为标准的C#代码,其他部分是普通的html代码
  2,用法:

    @{
string a = "abc";} @a @{C#代码块} //有标签的就是html代码
    @Model    //控制器传递来的对象
    @Model.dog.Name    //控制器传递来的dog对象的Name属性的值
    @if(),@foreach()等C#语句

  3,在代码中输入大段文字

    两种方法:
      1,@:大段文字   //不推荐使用了,
      代码:

        if(Model.IsOK)        {          @:文字        }

      2,<html标签>文字</html标签>

      代码:

        if(Model.IsOK)        {          文字        }

    razor会智能识别哪块是C#,哪块是HTML,HTML中想运行C#代码就用@,想在C#中代码中输入HTML就写“HTML标签”。

    但是如果由于样式等原因不想加上额外的标签,那么可以用<text></text>标记,特殊的<text>不会输出到Html中。
  4,注意:不要在@item后写分号 //分号会被当成html代码,原样输出
  5,razor会自动识别哪块是普通字符,哪块是表达式,主要就是根据特殊符号来分辨(“识别到这里是否能被当成一个合法的C#语句”)。

    例子:

      不能这样写 <a href="Course@CourseId.ashx">,否则ashx会被识别为CourseId的一个属性,
      应该加上()强制让引擎把CourseId识别成一个单独的语法,<a href="Course(@CourseId).ashx">

    技巧:

      不确定的地方就加上(),也可以按照编辑器的代码着色来进行分辨

  6,如果不能自动提示,把页面关掉再打开就可以了。如果还是不能自动提示,只要运行没问题就行。
    cshtml文件中如果有警告甚至错误,只要运行没问题就没关系
  7,<span>333@qq.com</span>,razor会自动识别出来是邮箱,所以razor不会把 @qq.com当成qq对象的com属性。
    但是对于特殊的邮箱或者就是要显示@,那么可以使用@转义@,也就是“@@”

      
  • item_@item.Length//会把@item.Length识别成邮箱,      因此用上()成为:      
  • item_@(item.Length)
  •   8,易错:

        要区分C#代码和html代码,

        正确的:style='display:(@message.IsHide?"none":"block")'

        错误的:style="display: (@message.IsHide) ? none : block"

        注意:

          为了避免C#中的字符串的“”和html的属性值的“”冲突,建议如果html属性中嵌入了C#代码,那么html的属性的值用单引号
      9,为了避免XSS攻击(跨站脚本攻击,在输出对象中嵌入script代码等恶意代码),Razor的@会自动把内容进行htmlencode输出,
        如果不想编码后输出,使用@Html.Raw()方法
      10,Razor的注释方法
        @*要注释的内容*@
      11,Razor中调用泛型方法的时候,由于<>会被认为是html转回标记模式,因此要用()括起来,比如@(Html.Test<string>)
        ()可以解决大部分问题,在View中一般不会调用复杂的方法
      12,如果cshtml中任何html标签的属性中以"~/"开头,则会自动进行虚拟路径的处理,
        当然一般是给<script>的src属性、<link>的href属性、<a>标签的href属性、<img>的src属性用的。
      13,html标签的任何属性的值如果是C#的值(使用@传递过来的值),
        如果是bool类型的值,那么如果值是false,则不会渲染这个属性,如果是true,则会渲染成“属性名=属性名”
        代码示例

        @{      bool b1 = true;      bool b2 = false;    }    //此时生成的html代码为:

        这个特性避免了进行三元运算符的判断

      14,总结:
        1、@就是C#,<aaa></aaa>就是html

        2、如果想让被识别成html的当成C#那就用@()

        3、如果想让被识别成C#的当成html,用<span>等标签,如果不想生成额外的标签,就用<text></text>

        4、如果不想对内容htmlencode显示就用@Html.Raw()方法

        5、属性的值如果以"~/"开头会进行虚拟路径处理

        6、属性值如果是bool类型,如果是false就不输出这个属性,如果true就输出“属性名=属性名”<input type="checkbox" checked="@b1"/>

    四、知识点补充和复习
      1,dynamic是C#语法中提供的一个语法,实现像JavaScript一样的动态语言,可以到运行的时候再去发现属性的值或者调用方法

        代码示例

        dynamic p = new dynamic();    p.Name = "rupeng.com";    p.Hello();

        注意:即使没有成员p.Age=3;编译也不会报错,只有运行的时候才会报错

          好处是灵活,坏处是不容易在开发的时候发现错误、并且性能低
        如果dynamic指向System.Dynamic.ExpandoObject()对象,这样可以给对象动态赋值属性(不能指向方法):

        dynamic p = new System.Dynamic.ExpandoObject();    p.Name = "rupeng.com";    p.Age = 10;    Console.WriteLine(p.Name+","+p.Age);

      2,var类型推断

        var i = 3;    var s ="abc";

        编译器会根据右边的类型推断出var是什么类型

        var和dynamic的区别:
          var是编译的时候确定的,dynamic是在运行的时候动态确定的
          var变量不能指向其他类型,dynamic可以(因为var在编译的时候已经确定了类型)
      3,匿名类型

        匿名类型是C#中提供的一个新语法:

        var p = new {Age=5,Name="rupeng.com"};//这样就创建了一个匿名类的对象,这个类没有名字,所以叫匿名类
        原理:
          编译器生成了这个类,这个类是internal、属性是只读的、初始值是通过构造函数传递的
        因此:
          因为匿名类的属性是只读的,所以匿名类型的属性是无法赋值的;
          因为匿名类型是internal,所以无法跨程序集访问其成员(只能活在自己当前的程序集内)。
    五、Controller给View传递数据的方式
      1,ViewData:
        以ViewData["name"]="rupeng";string s =(string)ViewData["name"]这样的键值对的方式进行数据传送
      2,ViewBag:
        ViewBag是dynamic类型的参数,是对ViewData一个动态类型封装,用起来更方便,和ViewData共同操作一个数据。ViewBag.name="";
        @ViewBag.name。
     

       用ViewBag传递数据非常方便,但是因为ASP.Net MVC中的“Html辅助类”等对于ViewBag有一些特殊约定,一不小心就跳坑了(http://www.cnblogs.com/rupeng/p/5138575.html),所以尽量不要用ViewBag,而是使用Model。

      3、Model:
        可以在Controller中通过return View(model)赋值,然后在cshtml中通过Model属性来访问这个对象;

        如果在cshtml中通过“@model 类型”(注意model小写)指定类型,则cshtml中的Model就是指定的强类型的,这样的cshtml叫“强类型视图”;

        如果没有指定“@model 类型”, 则cshtml中的Model就是dynamic。

    六、关于Action的参数
      ASP.Net MVC5会自动对参数做类型转换

      对于boolean类型的参数(或者Model的属性),如果使用checkbox,则value必须是“true”,否则值永远是false。对于double、int等类型会自动进行类型转换

      1,一个Controller可以有多个方法,这些方法叫Action。通过“Controller名字/方法名”访问的时候就会执行对应的方法。

      2,Action的三种类型的参数:
        普通参数、Model类、FormCollection

        1,普通参数:

          Index(string name,int age)。框架会自动把用户Get请求的QueryString或者Post表单中的值根据参数名字映射对应参数的值,
          适用于查询参数比较少的情况。

          注意:int类型的可空问题

        2,Model类。叫ViewModel。

        3,FormCollection,采用fc["name"]这种方法访问,类似于HttpHandler中用context["name"]。

          适用于表单元素不确定、动态的情况

      3,Action的方法不能重载,所以一个Controller中不能存在两个同名的Action

        错误代码:

          public ActionResult T1(string name)和public ActionResult T1(int Age)不能同时存在
        特殊情况:
          给Action方法上标注[HttpGet]、[HttpPost],注意当发出Get或者Post请求的时候就会执行相应标注的方法,变相实现了同名的Action

        常见的应用方法:

          把需要展示的初始页面的Action标注为[HttpGet],把表单提交的标注为[HttpPost]
      4,Action参数可以一部分是普通参数,一部分为Model
        代码示例:

          public ActionResult T1(string name,Classes className)

      5,Action参数如果在请求中没有对应的值,就会去默认值:

        Model类的形式则取默认值:int是0、boolean是false、引用类型是null。
        普通参数的形式:取默认值会报错,如果允许为空,要使用int?,也可以使用C#的可选参数语法来设定默认值
        示例代码:

          Index(string name="tom");

      6,上传文件的参数用HttpPostedFileBase类型,

    七、View的查找
      1,return View()会查找Views的Controller名字的Action的名字的cshtml
      2,return View("Action1"),查找Views的Controller名字下的“Action1.cshtml”,如果找不到则到特殊的shared文件夹下找“Action1.cshtml”
      3、return View("Action1")中如何传递model?return View("Action1",model)。
        陷阱:如果model传递的是string类型,则需要return View("Action1",(object)str)为什么?看一下重载!
        注意:
          return View("Action1")不是重定向,浏览器和服务器之间只发生了一次交互,地址栏还是旧的Action的地址。
          这和重定向return Redirct("/Index/Action1");不一样
        应用:

          执行报错,return View("Error",(object)msg) 通用的报错页面。为了防止忘了控制重载,封装成一个通用方法。

    八、其他类型的ActionResult

      1,View()是一个方法,它的返回值是ViewResult类型,ViewResult继承自ActionResult,

        如果在确认返回的是View(),返回值写成ViewResult也行,但是一般没这个必要,因为那样就不灵活了。因为ViewResult还有其他子类
      2,RedirectResult,重定向,最终就是调用response.Redirect()。
        用法:

          return Redirect("http://www.rupeng.com");//重定向到rupeng      return Redirect("~/1.html");//重定向到

      3,ContentResult

        返回程序中直接拼接生成的文本内容

        return Content(string content,string contentType)

      4,文件 return File();

      1,return File(byte[] fileContents,string contentType);//返回byte[]格式的数据
      2,return File(byte[] fileContents,string contentType,fileDownLoadName);//fileDownLoadName:设定浏览器端弹出的建议保存的文件名
      3,return File(Stream fileStream, string contentType) 返回Stream类型的数据(框架会帮着Dispose,不用也不能Dispose)
      4,FileStreamResult    return File(Stream fileStream,string contentType,string fileDownLoadName)
      5, File(string fileName, string contentType)// 返回文件名指定的文件,内部还是流方式读取文件;
      6, File(string fileName, string contentType, string fileDownloadName)  //如果是返回动态生成的图片(比如验证码),则不用设置fileDownloadName;如果是“导出学生名单”、“下载文档”等操作则要设定fileDownloadName。

      注意:如果在Controller中要使用System.IO下的File类,因为和File方法重名了,所以要用命名空间来引用了。

      5,return HttpNotFound();
      6,return JavaScript(string script);
        返回JavaScript代码字符串,和return Content("alert('Hello World');","application/x-javascript");效果一样。
        因为违反三层原则,尽量不要使用
      7,Json
        JsonResult Json(object data) 把data对象序列化为json字符串返回客户端,并且设置contentType为"application/json"
        Json方法默认是禁止Get请求的(主要为了防止CSRF攻击,举例:在A网站中嵌入一个请求银行网站给其他账号转账的Url的img),只能Post请求。所以如果以Get方式访问是会报错的。

        如果确实需要以Get方式方式,需要调用return Json(data, JsonRequestBehavior.AllowGet)

        ASP.NET MVC 默认的Json方法实现有如下的缺点:
          1,日期类型的属性格式化成字符串是“\/Date(1487305054403)\/"这样的格式,在客户端要用js代码格式化处理,很麻烦。

          2,json字符串中属性的名字和C#中的大小写一样,不符合js中“小写开头、驼峰命名”的习惯。在js中也要用大写去处理。

          3,无法处理循环引用的问题(尽管应该避免循环引用),会报错“序列化类型为***的对象时检测到循环引用”

      8,重定向

        1,Redirect(string url)
        2,RedirectToAction(string actionName,string controllerName);//其实就是帮助拼接生成url,最终还是调用Redirect(),

        3,两者的区别:

          RedirectToAction是让客户端重定向,是一个新的Http请求,所以无法读取ViewBag中的内容;
          return View()是一次服务器一次处理转移
          Redirect和return View 的区别:

            1、 Redirect是让浏览器重定向到新的地址;return View是让服务器把指定的cshtml的内容运行渲染后给到浏览器;

            2、 Redirect浏览器和服务器之间发生了两次交互;return View浏览器和服务器之间发生了1次交互

            3、 Redirect由于是两次请求,所以第一次设置的ViewBag等这些信息,在第二次是取不到;而View则是在同一个请求中,所以ViewBag信息可以取到。

            4、 如果用Redirect,则由于是新的对Controller/Action的请求,所以对应的Action会被执行到。如果用View,则是直接拿某个View去显示,对应的Action是不执行的。

          什么情况用View?服务器端产生数据,想让一个View去显示的;
          什么情况用Redirect?让浏览器去访问另外一个页面的时候。
    九、杂项Misc
      1、TempData
        在SendRedirect客户端重定向或者验证码等场景下,由于要跨请求的存取数据,是不能放到ViewBag、Model等中,
        需要“暂时存到Session中,用完了删除”的需求:实现起来也比较简单:

        存入:      Session["verifyCode"] = new Random().Next().ToString();
        读取:    String code = (string) Session["verifyCode"];    Session["verifyCode"] = null;    if(code==model.Code)    {      //...    }

        ASP.Net MVC中提供了一个TempData让这一切更简单。

        在一个Action存入TempData,在后续的Action一旦被读取一次,数据自动销毁。
        TempData默认就是依赖于Session实现的,所以Session过期以后,即使没有读取也会销毁。
        应用场景:验证码;
      2、HttpContext与HttpContextBase、HttpRequest与HttpRequestBase、HttpPostedFile与HttpPostedFileBase。

        注意:进行asp.net mvc开发的时候尽量使用****Base这些类,不要用asp.net内核原生的类。HttpContext.Current(X)

          1)在Controller中HttpContext是一个HttpContextBase类型的属性(真正是HttpContextWrapper类型,是对System.Web.HttpContext的封装),System.Web.HttpContext是一个类型。这两个类之间没有继承关系。

            System.Web.HttpContext类型是原始ASP.Net核心中的类,在ASP.Net MVC中不推荐使用这个类(也可以用)。

          2)HttpContextBase能“单元测试”,System.Web.HttpContext不能。

          3)怎么样HttpContextBase.Current?其实是不推荐用Current,而是随用随传递。

          4)HttpContextBase的Request、Response属性都是HttpRequestBase、HttpResponseBase类型。Session等也如此。

          5)如果真要使用HttpContext类的话,就要System.Web.HttpContext

      3,Views的web.config中的system.web.webpages.razor的pages/namespaces节点下配置add命名空间,这样cshtml中就不用using了

        示例代码:

          
            
            
              
                
                
                
                
                
              
            
          

      4,Layout布局文件

        @RenderBody()渲染正文部分;cshtml的Layout属性设定Layout页面地址;
        @RenderSection("Footer")用于渲染具体页面中用@section Footer{}包裹的内容,如果Footer是可选的,那么使用@RenderSection("Footer",false),
          可以用IsSectionDefined("Footer")实现“如果没定义则显示***”的效果。
      
      5, 可以在Views文件夹下建一个_ViewStart.cshtml文件,在这个文件中定义Layout,这样不用每个页面中都设定Layout,
        当然具体页面也可以通过设定Layout属性来覆盖默认的实现;
      6,@Html.DropDownList
        如果在页面中输出一个下拉列表或者列表框,就要自己写foreach拼接html,还要写if判断哪项应该处于选中状态

        

        asp.net mvc中提供了一些“Html辅助方法”(其实就是Controller的Html属性中的若干方法,其实是扩展方法)用来简化html代码的生成。

        DropDownList是生成下拉列表的。

          1)DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList)
            string name参数用来设定 <select>标签的name属性的值,id属性的值默认和name一致。
            下拉列表中的项(<option>)以SelectListItem集合的形式提供,SelectListItem的属性:
            bool Selected:是否选中状态,也就是是否生成selected="selected"属性;
            string Text:显示的值,也就是<option>的innerText部分;
            string Value:生成的value属性,注意是string类型;
            示例代码:

              List
    list = new List
    ();          list.Add(new Person { Id=1,Name="lily",IsMale=false});          list.Add(new Person { Id = 12, Name = "tom", IsMale = true });          list.Add(new Person { Id = 13, Name = "lucy", IsMale = false });          List
    sliList = new List
    ();          foreach (var p in list)          {            SelectListItem listItem = new SelectListItem();            listItem.Selected = (p.Id==2);            listItem.Text = p.Name;            listItem.Value = p.Id.ToString();            sliList.Add(listItem);          }          return View(sliList);
            @model IEnumerable
                                          
                
    DDL                                
                  @Html.DropDownList("pid", Model);            
                      

        2)DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, object htmlAttributes)

          htmlAttributes属性用来生成select标签的其他属性,通常以匿名类对象的形式提供,
          比如new { onchange = "javascript:alert('ok')", style = "color:red", aaa = "rupeng", id = "yzk",@class="warn error" }
          生成的html源码为:

          

          支持自定义属性,给你原样输出,具体什么含义自己定;

          由于class是关键字,所以不能直接用class="",要加上一个@前缀,这其实是C#中给变量名取名为关键字的一种语法;
          注意:
            id默认和name一致,如果设定了id则覆盖默认的实现。
        3)构造一个特殊的集合类SelectList,他会自动帮着做集合的遍历

          public ActionResult DDL2()      {        List
    list = new List
    ();        list.Add(new Person { Id=666,Name="zhangsan",IsMale=false});        list.Add(new Person { Id = 222, Name = "tom", IsMale = true });        list.Add(new Person { Id = 333, Name = "lucy", IsMale = false });        SelectList selectList = new SelectList(list, "Id", "Name");        return View(selectList);      }
          @Html.DropDownList("name",(SelectList)Model);

          IEnumerable items参数用来显示的原始对象数据,string dataValueField为“对象的哪个属性用做生成value属性”,

          string dataTextField为“对象的哪个属性用作生成显示的文本属性”。
          用SelectList的好处是简单,但是如果说要同时显示多个属性的时候,就只能用非SelectList的方式了。
          SelectList还可以设定第四个参数:
            哪个值被选中:SelectList selectList = new SelectList(list,"Id","Name",222);
          一个坑:不能让cshtml中的DropDownList的第一个name参数和ViewBag中任何一个属性重名http://www.cnblogs.com/rupeng/p/5138575.html。
          建议不要通过ViewBag传递,都通过Model传递
      7,@Html.ListBox()
          和@Html.DropDownList()类似
      8,为什么不再推荐使用“Html辅助方法”
        坏处:因为不符合复杂项目的开发流程(前端程序员可能看不懂),
        好处:可以把表单验证、绑定等充分利用起来,开效率高,
          但是在互联网项目中开发效率并不是唯一关注因素。在asp.net mvc6中已经不再推荐使用html辅助方法的表单了
      9,Request.IsAjaxRequest()
        判断是来自于Ajax请求,这样可以让ajax请求和非ajax请求响应不同的内容
        原理:
          Ajax请求的报文头中有x-requested-with: XMLHttpRequest。
          如果使用System.Web.HttpContext,那么是没有这个方法的,那么自己就从报文头中取数据判断。
        示例代码:

          public ActionResult Ajax1()      {        return View();      }      public ActionResult Ajax2()      {        Person p = new Person();        p.Name = "rupeng";        if (Request.IsAjaxRequest())        {          return Json(p);        }        else        {          return Content(p.Name);        }      }

      10,数据验证

        1,asp.net mvc会自动根据属性的类型进行基本的校验,比如如果属性是int类型的,那么在提交非整数类型的数据的时候就会报错。
          注意ASP.net MVC并不是在请求验证失败的时候抛异常,而是把决定权交给程序员,程序员需要决定如何处理数据校验失败。
          在Action中根据ModelState.IsValid判断是否验证通过,如果没有通过下面的方法拿到报错信息
          示例代码:

          public ActionResult Index(IndexModel model)      {        if (ModelState.IsValid)        {          return Content("Age=" + model.Age);        }        else        {          return Content("验证失败");        }      }

          在参数很多的情况下使用下面的封装的方法:

          public static string GetValidMsg(ModelStateDictionary modelState)      {        StringBuilder sb = new StringBuilder();        foreach (var propName in modelState.Keys)        {          if (modelState[propName].Errors.Count <= 0)          {            continue;          }          sb.Append("属性【").Append(propName).Append("】错误:");          foreach (var modelError in modelState[propName].Errors)          {            sb.AppendLine(modelError.ErrorMessage);          }        }        return sb.ToString();      }

     

        2,ASP.Net MVC提供了在服务器端验证请求数据的能力。要把对应的Attribute标记到Model的属性上(标记到方法参数上很多地方不起作用)。

          常用验证Attribute:
            a) [Required]   这个属性是必须的

            b) [StringLength(100)],  字符串最大长度100;[StringLength(100,MinimumLength=10)]长度要介于10到100之间

            c) [RegularExpression(@"aa(\d)+bb")]   正则表达式

            d) [Range(35,88)]  数值范围。字符串长度范围的话请使用[StringLength(100,MinimumLength=10)]

            e) [Compare("Email")]  这个属性必须和Email属性值一样。

            f) [EmailAddress]   要是邮箱地址

            g) [Phone]  电话号码,规则有限

          示例代码:

          public class IndexModel      {        [Required]        public int Age { get; set; }        public long Id { get; set; }        public string Name { get; set; }        [StringLength(11)]        public string PhoneNum { get; set; }      }

        3, 验证Attribute上都有ErrorMessage属性,用来自定义报错信息。ErrorMessage中可以用{0}占位符作为属性名的占位。

          示例代码:

          [Required(ErrorMessage="不能为空")]      public int Age { get; set; }

        4, 数据验证+Html辅助类高级控件可以实现很多简化的开发,连客户端+服务器端校验都自动实现了,但是有点太“WebForm”了,因此这里先学习核心原理,避免晕菜。

      11,自定义验证规则ValidationAttribute,
        自动的验证规则需要直接或者间接继承自ValidationAttribute
        1,使用正则表达式的校验,直接从RegularExpressionAttribute继承

          示例代码:        public class QQNumberAttribute : RegularExpressionAttribute        {          public QQNumberAttribute() : base(@"^\d{5,10}$")//不要忘了^$          {            this.ErrorMessage = "{0}属性不是合法的QQ号,QQ号需要5-10位数字";            //设定ErrorMessage的默认值。使用的人也可以覆盖这个值          }        }

          手机号的正则表达式:@"^1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])\d{8}$"

        2,直接继承自ValidationAttribute,重写IsValid方法

          比如校验中国电话号码合法性

          public class CNPhoneNumAttribute : ValidationAttribute      {        public CNPhoneNumAttribute()        {          this.ErrorMessage = "电话号码必须是固话或者手机,固话要是3-4位区号开头,手机必须以13、15、18、17开头";        }        public override bool IsValid(object value)        {          if (value is string)          {            string s = (string)value;            if (s.Length == 13)//手机号            {              if (s.StartsWith("13") || s.StartsWith("15") || s.StartsWith("17") || s.StartsWith("18"))              {                return true;              }              else              {                return false;              }            }            else if (s.Contains("-"))//固话            {              string[] strs = s.Split('-');              if (strs[0].Length==3||strs[0].Length==4)              {                return true;              }              else              {                return false;              }            }            else            {              return false;            }          }          else          {            return false;          }          //return base.IsValid(value);        }      }

     

        3,还可以让Model类实现IValidatableObject接口,用的比较少

    十、过滤器(Filter)

        AOP(面向切面编程)是一种架构思想,用于把公共的逻辑放到一个单独的地方,这样就不用每个地方都写重复的代码了。

        比如程序中发生异常,不用每个地方都try...catch...只要在(Global 的Application_Error)中统一进行异常处理。不用每个Action中都检查当前用户是否有执行权限,
        ASP.net MVC中提供了一个机制,每个Action执行之前都会执行我们的代码,这样统一检查即可。

      1,四种Filter

        在ASP.Net MVC中提供了四个Filter(过滤器)接口实现了这种AOP机制:
          IAuthorizationFilter、IActionFilter、IResultFilter、IExceptionFilter。

     

        1,IAuthorizationFilter

          一般用来检查当前用户是否有Action的执行权限,在每个Action被执行前执行OnAuthorization方法;

     

        2,IActionFilter

          也是在每个Action被执行前执行OnActionExecuting方法,每个Action执行完成后执行OnActionExecuted方法
          和IAuthorizationFilter的区别是IAuthorizationFilter在IActionFilter之前执行,检查权限一般写到IAuthorizationFilter中;
        3,IResultFilter,在每个ActionResult的前后执行IResultFilter。用的很少,后面有一个应用。
        4,IExceptionFilter,当Action执行发生未处理异常的时候执行OnException方法。
          在ASP.net MVC 中仍然可以使用“Global 的Application_Error”,但是建议用IExceptionFilter。

      2、IAuthorizationFilter案例:只有登录后才能访问除了LoginController之外的Controller。

        1,编写一个类CheckAuthorFilter,实现IAuthorizationFilter接口(需要引用System.Web.Mvc程序集)

          示例代码:

          public class CheckLoginFilter : IAuthorizationFilter      {        public void OnAuthorization(AuthorizationContext filterContext)        {          string ctrlName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;          string actionName = filterContext.ActionDescriptor.ActionName;          if (ctrlName=="Login"&&(actionName=="Index"||actionName=="Login"))          {            //什么都不做          }          else          {            if (filterContext.HttpContext.Session["username"]==null)            {              ContentResult contentResult = new ContentResult();              contentResult.Content = "没有登录";              //filterContext.Result = contentResult;              filterContext.Result = new RedirectResult("/Login/Index");            }          }        }      }

        2,在Globel中注册这个Filter:GlobalFilters.Filters.Add(new CheckAuthorFilter());

          示例代码:

          protected void Application_Start()      {        AreaRegistration.RegisterAllAreas();        RouteConfig.RegisterRoutes(RouteTable.Routes);        GlobalFilters.Filters.Add(new CheckLoginFilter());      }

        3,CheckAuthorFilter中实现OnAuthorization方法。

          filterContext.ActionDescriptor  可以获得Action的信息:

          filterContext.ActionDescriptor.ActionName  获得要执行的Action的名字;

          filterContext.ActionDescriptor.ControllerDescriptor.ControllerName  为要执行的Controller的名字;

          filterContext.ActionDescriptor.ControllerDescriptor.ControllerType  为要执行的Controller的Type;

          filterContext.HttpContext  获得当前请求的HttpContext;

          如果给“filterContext.Result”赋值了,那么就不会再执行要执行的Action,而是以“filterContext.Result”的值作为执行结果
          (注意如果是执行的filterContext.HttpContext.Response.Redirect(),那么目标Action还会执行的)。
        4,检查当前用户是否登录,
          如果没有登录则filterContext.Result = new ContentResult() { Content = "没有权限" };
          或者filterContext.Result = new RedirectResult("/Login/Index");
          (最好不要filterContext.HttpContext.Response.Redirect("/Login/Index");)
        5,A用户有一些Action执行权限,B用户有另外一些Action的执行权限;

      3、IActionFilter案例:日志记录,记录登录用户执行的Action的记录,方便跟踪责任。

      4、IExceptionFilter案例:记录未捕获异常。

        public class ExceptionFilter : IExceptionFilter    {      public void OnException(ExceptionContext filterContext)      {        File.AppendAllText("d:/error.log", filterContext.Exception.ToString());        filterContext.ExceptionHandled = true;//如果有其他的IExceptionFilter不再执行        filterContext.Result = new ContentResult() { Content= "error" };      }    }

        然后:

        GlobalFilters.Filters.Add(new ExceptionFilter());

     

      5、总结好处:

          一次编写,其他地方默认就执行了。可以添加多个同一个类型的全局Filter,按照添加的顺序执行。

      6、(*)非全局Filter:

        只要让实现类继承自FilterAttribute类,然后该实现哪个Filter接口就实现哪个(四个都支持)。
        不添加到GlobalFilters中,而是把这个自定义Attribute添加到Controller类上这样就只有这个Controller中操作会用到这个Filter。
        如果添加到Action方法上,则只有这个Action执行的时候才会用到这个Filter。

    Nuget笔记

    一、NuGet简介

      我们进行软件开发的时候,经常会用到第三方的开发包(俗称dll),比如NPOI、MYSQL ADO.net 驱动等。

      如果自己去网上下载的问题:不好搜,不好找;容易下载到错误的;要自己进行安装配置;要选择和当前环境一致的版本(比如有的开发包在.net 2.0和.net 4.5中要用不同的版本);这个开发包可能还要使用其他的安装包。

      微软提供了NuGet这个软件,自动帮我们进行开发包的下载、安装,并且会根据当前的环境找到合适的版本,下载相关的依赖的开发包,还可以自动更新最新版本。

      NuGet在VS2013以上都提供了。有图形界面和命令行两种使用方式。

    二、Nuget.org寻宝

        http://www.nuget.org
    三、图形界面,进行安装

      在项目的“引用”中右键——管理Nuget程序包——在浏览中输入程序包的名字(比如NPOI)——搜索——然后进行安装

      由于nuget的服务器在国外,可能你的网络连不上或者速度慢,可以从其他镜像站点下

      方法:

        1,找一个可用的镜像站点,目前可用的是博客园的镜像,地址https://nuget.cnblogs.com/v3/index.json

        2,在VS的【工具】→【选项】→【NuGet包管理器】→【程序包源】,点击【添加】

          然后在搜索安装的时候在【程序包源】中选择我们添加的镜像

        3,在搜索结果中找到合适的结果,点击后在右侧选择合适的版本

          点击【安装】以后可能会弹出要求【同意】协议的对话框,点击【同意】即可。
          在【输出】的【程序包管理器】中出现“========== 已完成 ==========”的时候说明安装完成,有可能会报错。

        4,会自动添加引用

        5,有的安装包会自动修改APP.config等配置文件

        6,Nuget安装包信息在packages.config中,对应的安装包在解决方案的packages文件夹下,把项目拷给别人的时候没必要拷packages文件夹,别人拿到以后会自动下载恢复这些安装包。

        7,如果要删除某个安装包,不能只删除引用,否则还会自动恢复、添加,还要手动删除packages.config中的内容。最好使用图形界面的“卸载”功能。

    四、命令行的使用

      好处:方便、灵活

      1,在【程序包管理器控制台】视图中(如果没显示出来,则主菜单【工具】→【NuGet包管理器】→【程序包管理器控制台】)。

        输入:Install-Package 程序包的名字
        安装完成的标志

      2,可以在【程序包源】中指定镜像站点,【默认项目】指的是安装到哪个项目中

      3,指定版本::Install-Package 安装包 -Version 版本号,
        比如:
          Install-Package MySql.Data -Version 6.8.8

      4,卸载

        示例代码:
          UnInstall-Package MySql.Data

    Entity Framework笔记
      Entity Framework是在.Net平台下进行数据库开发的框架,ORM框架
    一、相关知识复习
      1. var类型推断:

        var p =new Person();

      2. 匿名类型。

        var a =new {p.Name,Age=5,Gender=p.Gender,Name1=a.Name};//{p.Name}=={Name=p.Name}

     

      3. 给新创建对象的属性赋值的简化方法:

        Person p = new Person{Name="tom",Age=5};

        等价于

        Person p = new Person();p.Name="tom";p.Age=5;

      4. lambda表达式

        函数式编程,在Entity Framework编程中用的很多

        1,原始样式

          Action
    a1 = delegate(int i){Console.writeLine(i);};

        2,可以简化为(=>读作goes to)

          Action
    a2 = (int i)=>{Console.writeLine(i);};

        3,还可以省略参数类型(编译器会自动根据委托类型解析):

          Action
    a3 =(i)=>{Console.writeLine(i);};

        4,如果只有一个参数还可以省略参数的小括号(多个参数不行)

          Action
    a4 = i=>{Console.writeLine(i);};

        5,如果委托有返回值,并且方法体只有一行代码,这一行代码还是返回值,那么就可以连方法的大括号和return都省略:

          Func
    f1 = delegate(int i,int j){
    return "结果是"+(i+j);};//原始形式      Func
    f2 = (i,j)=>"结果是"+(i+j);//简化形式

      5,集合常用的扩展方法

        Where(支持委托)、Select(支持委托)、Max、Min、OrderBy

        First(获取第一个,如果没有则异常)

        FirstOrDefault(获取第一个,如果没有则返回默认值)

        Single(获取唯一一个,如果没有或者多个则异常)

        SingleOrDefault(获取唯一一个,如果没有则返回默认值,多个则异常)

        注意:
          lambda中照样要避免变量名重名的问题:

          var p = persons.Where(p=>p.Name=="rupeng.com").First();//错误代码:两个p重名,修改一个

    二、 高级集合扩展方法

      准备工作1:创建对象类

      //学生  public class Person  {    public string Name { get; set; }    public int Age { get; set; }    public bool Gender { get; set; }    public int Salary { get; set; }    public override string ToString()    {      return string.Format("Name={0},Age={1},Gender={2},Salary={3}",Name, Age, Gender, Salary);    }  }
      //老师  public class Teacher  {    public Teacher()    {      this.Students=new List
    ();    }    public string Name { get; set; }    public List
    Students { get; set; }  }

     

      //准备工作2,在控制台项目中,添加数据

      var s0 =new Person { Name="tom",Age=3,Gender=true,Salary=6000};  var s1 = new Person { Name = "jerry", Age = 8, Gender = true, Salary = 5000 };  var s2 = new Person { Name = "jim", Age = 3, Gender = true, Salary = 3000 };  var s3 = new Person { Name = "lily", Age = 5, Gender = false, Salary = 9000 };  var s4 = new Person { Name = "lucy", Age = 6, Gender = false, Salary = 2000 };  var s5 = new Person { Name = "kimi", Age = 5, Gender = true, Salary = 1000 };  List
    list = new List
    ();  list.Add(s0);  list.Add(s1);  list.Add(s2);  list.Add(s3);  list.Add(s4);  list.Add(s5);  Teacher t1 = new Teacher { Name="如鹏网张老师"};  t1.Students.Add(s1);  t1.Students.Add(s2);  Teacher t2 = new Teacher { Name = "如鹏网刘老师" };  t2.Students.Add(s2);  t2.Students.Add(s3);  t2.Students.Add(s5);  Teacher[] teachers={t1,t2};

     

      //开始展示用法
        1,Any(),
          判断集合是否包含元素,返回值是bool,一般比Coun()>0效率高。
          Any还可以指定条件表达式。

          bool b = list.Any(p => p.Age > 50);等价于bool b = list.Where(p=>p.Age>50).Any();

        2,Distinct(),剔除完全重复数据。(*)注意自定义对象的Equals问题:需要重写Equals和GetHashCode方法来进行内容比较。

        3,排序:
          升序

            list.OrderBy(p=>p.Age);

          降序

            list.OrderByDescending(p=>p.Age)

            

        指定多个排序规则,不是多个OrderBy,而是:OrderBy..ThenBy

        list.OrderByDescending(p=>p.Age).ThenBy(p=>p.Salary),//也支持ThenByDescending()。注意这些操作不会影响原始的集合数据。

        4,Skip(n)

          跳过前n条数据;
          Take(n)获取最多n条数据,如果不足n条也不会报错。
          常用来分页获取数据。list.Skip(30).Take(20) 跳过前3条数据获取2条数据。
        5,Except(items1)
          排除当前集合中在items1中存在的元素
        6,Union(items1)
          把当前集合和items1中组合
        7,Intersect(items1)
          把当前集合和items1中取交集
        8,分组

          foreach(var g in list.GroupBy(p => p.Age))      {        Console.WriteLine(g.Key+":"+g.Average(p=>p.Salary));      }

        9,SelectMany:

          把集合中每个对象的另外集合属性的值重新拼接为一个新的集合

          foreach(var s in teachers.SelectMany(t => t.Students))      {        Console.WriteLine(s);//每个元素都是Person      }

        注意:

          不会去重,如果需要去重则要自己再次调用Distinct()
        10,Join
          //准备工作1:创建类

          //Master类      class Master      {        public long Id{
    get;set;}        public string Name{
    get;set;}      }
          //Dog类      class Dog      {        public long Id { get; set; }        public long MasterId { get; set; }        public string Name { get; set; }      }

     

          //准备工作2,在控制台项目中添加数据

          Master m1 = new Master { Id = 1, Name = "杨中科" };      Master m2 = new Master { Id = 2, Name = "比尔盖茨" };      Master m3 = new Master { Id = 3, Name = "周星驰" };      Master[] masters = { m1,m2,m3};      Dog d1 = new Dog { Id = 1, MasterId = 3, Name = "旺财" };      Dog d2 = new Dog { Id = 2, MasterId = 3, Name = "汪汪" };      Dog d3 = new Dog { Id = 3, MasterId = 1, Name = "京巴" };      Dog d4 = new Dog { Id = 4, MasterId = 2, Name = "泰迪" };      Dog d5 = new Dog { Id = 5, MasterId = 1, Name = "中华田园" };      Dog[] dogs = { d1, d2, d3, d4, d5 };

          Join可以实现和数据库一样的Join效果,对有关联关系的数据进行联合查询

          下面的语句查询所有Id=1的狗,并且查询狗的主人的姓名。

          var result = dogs.Where(d => d.Id > 1).Join(masters, d => d.MasterId, m => m.Id,(d,m)=>new {DogName=d.Name,MasterName=m.Name});      foreach(var item in result)      {        Console.WriteLine(item.DogName+","+item.MasterName);      }

    三、Linq

      1,简介
        查询Id>1的狗有如下两种写法:

          1)var r1 = dogs.Where(d => d.Id > 1);
          2)var r2 = from d in dogs              where d.Id>1              select d;

        第一种写法是使用lambda的方式写的,官方没有正式的叫法,我们就叫“lambda写法”;

        第二种是使用一种叫Linq(读作:link)的写法,是微软发明的一种类似SQL的语法,给我们一个新选择。

        两种方法是可以互相替代的,没有哪个好、哪个坏,看个人习惯。

        经验:

          需要join等复杂用法的时候Linq更易懂,一般的时候“lambda写法”更清晰,更紧凑

      2,辟谣
        “Linq被淘汰了”是错误的说法,应该是“Linq2SQL被淘汰了”。
        linq就是微软发明的这个语法,可以用这种语法操作很多数据,
        操作SQL数据就是Linq2SQL,linq操作后面学的EntityFramework就是Linq2Entity,linq操作普通.Net对象就是Linq2Object、Linq操作XML文档就是Linq2XML。
      3,linq基本语法
        以from item in items开始,items为待处理的集合,item为每一项的变量名;
        最后要加上select,表示结果的数据;记得select一定要最后。这是刚用比较别扭的地方。
        用法:

        1,var r= from d in dogs        select d.Id;
        2,var r = from d in dogs        select new{d.Id,d.Name,Desc="一条狗"};
        3,排序      var items = from d in dogs              //orderby d.Age             //orderby d.Age descending             orderby d.Age,d.MasterId descending             select d;
        4,join      var r9 = from d in dogs          join m in masters on d.MasterId equals m1.Id          select new { DogName=d.Name,MasterName=m.Name};

          注意:

            join中相等不要用==,要用equals。
            写join的时候linq比“lambda” 漂亮

        5,group by      var r1 = from p in list            group p by p.Age into g             select new { Age = g.Key, MaxSalary = g.Max(p=>p.Salary), Count = g.Count() };

      4、混用

        只有Where,Select,OrderBy,GroupBy,Join等这些能用linq写法,
        如果要用下面的“Max,Min,Count,Average,Sum,Any,First,FirstOrDefault,Single,SingleOrDefault,Distinct,Skip,Take等”则还要用lambda的写法
        (因为编译后是同一个东西,所以当然可以混用)。

        var r1 = from p in list        group p by p.Age into g        select new { Age = g.Key, MaxSalary = g.Max(p=>p.Salary), Count = g.Count() };    int c = r1.Count();    var item = r1.SingleOrDefault();    var c = (from p in list        where p.Age>3        select p        ).Count();

    四、 C#6.0语法

      1. 属性的初始化“public int Age{get;set;}=6”。低版本.Net中怎么办?构造函数
      2. nameof:可以直接获得变量、属性、方法等的名字的字符串表现形式。获取的是最后一段的名称。如果在低版本中怎么办?
        好处:可以避免写错,有利于编译时查看
        应用案例:ASP.Net MVC中的[Compare("BirthDay")]改成[Compare(nameof(BirthDay))]
      3,??语法
        int j = i ?? 3; 如果i为null则表达式的值为3,否则表达式的值就是i的值。如果在低版本中怎么办?int j = (i == null)?3:(int)i;
        应用案例:

          string name = null;Console.WriteLine(name??"未知");

      4, ?.语法:

        string s8 = null;    string s9 = s8?.Trim();     //如果s8为null,则不执行Trim(),让表达式的结果为null。

        在低版本中怎么办?

            string s9 = null;        if (s8 != null)        {          s9 = s8.Trim();        }

    五、Entity Framework简介

      1、 ORM:Object Relation Mapping ,通俗说:用操作对象的方式来操作数据库。
      2、 插入数据库不再是执行Insert,而是类似于

        Person p = new Person();    p.Age=3;p.Name="如鹏网";    db.Save(p);

        这样的做法。

      3、 ORM工具有很多Dapper、PetaPoco、NHibernate,最首推的还是微软官方的Entity Framework,简称EF。
      4、 EF底层仍然是对ADO.Net的封装。EF支持SQLServer、MYSQL、Oracle、Sqlite等所有主流数据库。
      5、 使用EF进行数据库开发的时候有两个东西建:建数据库(T_Persons),建模型类(Person)。根据这两种创建的先后顺序有EF的三种创建方法
        a) DataBase First(数据库优先):先创建数据库表,然后自动生成EDM文件,EDM文件生成模型类。简单展示一下DataBase First的使用。
        b) Model First(模型优先):先创建Edm文件,Edm文件自动生成模型类和数据库;
        c) Code First(代码优先):程序员自己写模型类,然后自动生成数据库。没有Edm。
        DataBase First简单、方便,但是当项目大了之后会非常痛苦;Code First入门门槛高,但是适合于大项目。Model First……
      6, Code First的微软的推荐用法是程序员只写模型类,数据库由EF帮我们生成,当修改模型类之后,EF使用“DB Miguration”自动帮我们更改数据库。
        但是这种做法太激进,不适合很多大项目的开发流程和优化,只适合于项目的初始开发阶段。

        Java的Hibernate中也有类似的DDL2SQL技术,但是也是用的较少。“DB Miguration”也不利于理解EF,因此在初学阶段,我们将会禁用“DB Miguration”,采用更实际的“手动建数据库和模型类”的方式。

      7, 如果大家用过NHibernate等ORM工具的话,会发现开发过程特别麻烦,需要在配置文件中指定模型类属性和数据库字段的对应关系,哪怕名字完全也一样也要手动配置。
        使用过Java中Struts、Spring等技术的同学也有过类似“配置文件地狱”的感觉。
        像ASP.Net MVC一样,EF也是采用“约定大于配置”这样的框架设计原则,省去了很多配置,能用约定就不要自己配置。
    六、 EF的安装
      1、 基础阶段用控制台项目。使用NuGet安装EntityFramework。会自动在App.config中中增加两个entityFramework相关配置段;
      2、 在web.config的Connection中配置连接字符串

        

    七、 EF简单DataAnnotations实体配置

      1、 数据库中建表T_Perons,有Id(主键,自动增长)、Name、CreateDateTime字段。

      2、 创建Person类

        [Table("T_Persons")]//因为类名和表名不一样,所以要使用Table标注    public class Person        {      public long Id { set; get; }      public string Name { get; set; }      public DateTime CreateDateTime { get; set; }    }

        因为EF约定主键字段名是Id,所以不用再特殊指定Id是主键,如果非要指定就指定[Key]。

        因为字段名字和属性名字一致,所以不用再特殊指定属性和字段名的对应关系,如果需要特殊指定,则要用[Column("Name")]

        (*)必填字段标注[Required]、字段长度[MaxLength(5)]、可空字段用int?、如果字段在数据库有默认值,则要在属性上标注[DatabaseGenerated]

          注意实体类都要写成public,否则后面可能会有麻烦。
      3,创建DbContext类(模型类、实体类)

        public class MyDbContext:DbContext    {      public MyDbContext():base("name=conn1")//name=conn1表示使用连接字符串中名字为conn1的去连接数据库      {      }      public DbSet
    Persons { get; set; }//通过对Persons集合的操作就可以完成对T_Persons表的操作    }

      4,运行测试

        MyDbContext ctx = new MyDbContext();    Person p = new Person();    p.CreateDateTime = DateTime.Now;    p.Name = "rupeng";    ctx.Persons.Add(p);    ctx.SaveChanges();

      注意:

        MyDbContext对象是否需要using有争议,不using也没事。每次用的时候new MyDbContext就行,不用共享同一个实例,共享反而会有问题。SaveChanges()才会把修改更新到数据库中。

      异常的处理:

        如果数据有错误可能在SaveChanges()的时候出现异常,一般仔细查看异常信息或者一直深入一层层的钻InnerException就能发现错误信息。

      举例:

        创建一个Person对象,不给Name、CreateDateTime赋值就保存。

    八、EF模型的两种配置方式

      EF中的模型类的配置有DataAnnotations、FluentAPI两种。

      DataAnnotations:

          [Table("T_Persons")]、[Column("Name")]这种在类上或者属性上标记的方式就叫DataAnnotations

      好处与坏处:
        这种方式比较方便,但是耦合度太高,不适合大项目开发。
      一般的类最好是POCO
        (Plain Old C# Object没有继承什么特殊的父类,没有标注什么特殊的Attribute,没有定义什么特殊的方法,就是一堆普通的属性);
      不符合大项目开发的要求。微软推荐使用FluentAPI的使用方式,因此后面主要用FluentAPI的使用方式。
    九、FluentAPI配置T_Persons的方式
      1,数据库中建表T_Perons,有Id(主键,自动增长)、Name、CreateDateTime字段。
      2,创建Person类。模型类就是普通C#类

        public class Person    {      public long Id { set; get; }      public string Name { get; set; }      public DateTime CreateDateTime { get; set; }    }

      3,创建一个PersonConfig类,放到ModelConfig文件夹下(PersonConfig、EntityConfig这样的名字都不是必须的)

        class PersonConfig: EntityTypeConfiguration
        {      public PersonConfig()      {        this.ToTable("T_Persons");//等价于[Table("T_Persons")]      }    }

     

      4,创建DbContext类

        public class MyDbContext:DbContext    {      public MyDbContext():base("name=conn1")      {      }      protected override void OnModelCreating(DbModelBuilder modelBuilder)      {        base.OnModelCreating(modelBuilder);        modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly());        //代表从这句话所在的程序集加载所有的继承自EntityTypeConfiguration为模型配置类。      }      public DbSet
    Persons { get; set; }    }

      5,运行测试

        MyDbContext ctx = new MyDbContext();    Person p = new Person();    p.CreateDateTime = DateTime.Now;    p.Name = "rupeng";    ctx.Persons.Add(p);    ctx.SaveChanges();

        和以前唯一的不同就是:

        模型不需要标注Attribute;
        编写一个XXXConfig类配置映射关系;DbContext中override OnModelCreating;

      6,多个表怎么办?

        创建多个表的实体类、Config类,并且在DbContext中增加多个DbSet类型的属性即可。

    十、EF的基本增删改查
      获取DbSet除了可以ctx.Persons之外,还可以ctx.Set<Person>()
      1,增加,同上
        注意:
          如果Id是自动增长的,创建的对象显然不用指定Id的值,并且在SaveChanges ()后会自动给对象的Id属性赋值为新增行的Id字段的值。
      2,删除。
        先查询出来要删除的数据,然后Remove。这种方式问题最少,虽然性能略低,但是删除操作一般不频繁,不用考虑性能。后续在“状态管理”中会讲其他实现方法

        MyDbContext ctx = new MyDbContext();    var p1= ctx.Persons.Where(p => p.Id == 3).SingleOrDefault();//先查询出来    if(p1==null)    {      Console.WriteLine("没有id=3的人");    }    else    {      ctx.Persons.Remove(p1);//进行删除    }    ctx.SaveChanges();//删除后进行保存

        批量删除:

          怎么批量删除,比如删除Id>3的?

            查询出来一个个Remove。性能坑爹。如果操作不频繁或者数据量不大不用考虑性能,如果需要考虑性能就直接执行sql语句(后面讲)

      3,修改:先查询出来要修改的数据,然后修改,然后SaveChanges()

        MyDbContext ctx = new MyDbContext();    var ps = ctx.Persons.Where(p => p.Id > 3);    foreach(var p in ps)    {      p.CreateDateTime = p.CreateDateTime.AddDays(3);      p.Name = "haha";    }    ctx.SaveChanges();

     

      4,查。

        因为DbSet实现了IQueryable接口,而IQueryable接口继承了IEnumerable接口,所以可以使用所有的linq、lambda操作。给表增加一个Age字段,然后举例orderby、groupby、where操作、分页等。一样一样的。

      5,查询order by的一个细节

        EF调用Skip之前必须调用OrderBy:

        如下调用

          var items = ctx.Persons.Skip(3).Take(5); //会报错“The method 'OrderBy' must be called before the method 'Skip'.)”,

        要改成:

          var items = ctx.Persons.OrderBy(p=>p.CreateDateTime).Skip(3).Take(5);

     

        这也是一个好习惯,因为以前就发生过(写原始sql):

          分页查询的时候没有指定排序规则,以为默认是按照Id排序,其实有的时候不是,就造成数据混乱。写原始SQL的时候也要注意一定要指定排序规则。

    十一、EF原理及SQL监控
      EF会自动把Where()、OrderBy()、Select()等这些编译成“表达式树(Expression Tree)”,然后会把表达式树翻译成SQL语句去执行。
      (编译原理,AST)因此不是“把数据都取到内存中,然后使用集合的方法进行数据过滤”,因此性能不会低。但是如果这个操作不能被翻译成SQL语句,则或者报错,或者被放到内存中操作,性能就会非常低。
      怎么查看真正执行的SQL是什么样呢?
        DbContext有一个Database属性,其中的Log属性,是Action<String>委托类型,也就是可以指向一个void A(string s)方法,其中的参数就是执行的SQL语句,每次EF执行SQL语句的时候都会执行Log。因此就可以知道执行了什么SQL。

      EF的查询是“延迟执行”的,只有遍历结果集的时候才执行select查询,ToList()内部也是遍历结果集形成List。

      (如果要立刻开始执行,可以在后面加上ToList(),因为它会遍历集合)

      查看Update操作,会发现只更新了修改的字段。

        观察一下前面学学习时候执行的SQL是什么样的。Skip().Take()被翻译成了?Count()被翻译成了?

        var result = ctx.Persons.Where(p => p.Name.StartsWith("rupeng"));//看看翻译成了什么? like语句    var result = ctx.Persons.Where(p => p.Name.Contains("com"));//呢? %com%    var result = ctx.Persons.Where(p => p.Name.Length>5); //呢?     var result = ctx.Persons.Where(p => p.CreateDateTime>DateTime.Now);// 呢?

      再看看(好牛):

        long[] ids = { 2,5,6};//不要写成int[]    var result = ctx.Persons.Where(p => ids.Contains(p.Id));

      EF中还可以多次指定where来实现动态的复合检索:

        //必须写成IQueryable
    ,如果写成IEnumerable就会在内存中取后续数据    IQueryable
    items = ctx.Persons;//为什么把IQueryable
    换成var会编译出错    items = items.Where(p=>p.Name=="rupeng");    items = items.Where(p=>p.Id>5);

      (*)EF是跨数据库的,如果迁移到MYSQL上,就会翻译成MYSQL的语法。要配置对应数据库的Entity Framework Provider。

      细节:
        每次开始执行的__MigrationHistory等这些SQL语句是什么?
        是DBMigration用的,也就是由EF帮我们建数据库,现在我们用不到,用下面的代码禁用:

        Database.SetInitializer
    (null);//XXXDbContext就是项目DbContext的类名。一般建议放到XXXDbContext构造函数中。

        注意这里的Database是System.Data.Entity下的类,不是DbContext的Database属性。如果写到DbContext中,最好用上全名,防止出错。

    十二、执行原始的SQL

      在一些特殊场合,需要执行原生SQL。
      执行非查询语句,调用DbContext 的Database属性的ExecuteSqlCommand方法,可以通过占位符的方式传递参数:

      ctx.Database.ExecuteSqlCommand("update T_Persons set Name={0},CreateDateTime=GetDate()", "rupeng.com");

      占位符的方式不是字符串拼接,经过观察生成的SQL语句,发现仍然是参数化查询,因此不会有SQL注入漏洞。

      示例代码:

        var q1 = ctx.Database.SqlQuery
    ("select Name,Count(*) Count from T_Persons where Id>{0} and CreateDateTime<={1} group by Name",2, DateTime.Now); //返回值是DbRawSqlQuery
    类型,也是实现了IEnumerable接口    foreach(var item in q1)    {      Console.WriteLine(item.Name+":"+item.Count);    }    class Item1    {      public string Name { get; set; }      public int Count { get; set; }    }

        类似于ExecuteScalar的操作比较麻烦:

          int c = ctx.Database.SqlQuery
    ("select count(*) from T_Persons").SingleOrDefault();

     

    十三、不是所有lambda写法都能被支持

      下面想把Id转换为字符串比较一下是否为"3"(别管为什么):

        var result = ctx.Persons.Where(p => Convert.ToString(p.Id)=="3");

        运行会报错(也许高版本支持了就不报错了),这是一个语法、逻辑上合法的写法,但是EF目前无法把他解析为一个SQL语句。

        出现“System.NotSupportedException”异常一般就说明你的写法无法翻译成SQL语句。

        想获取创建日期早于当前时间一小时以上的数据:

        var result = ctx.Persons.Where(p => (DateTime.Now - p.CreateDateTime).TotalHours>1);

        同样也可能会报错。

      怎么解决?
        尝试其他替代方案(没有依据,只能乱试):

        var result = ctx.Persons.Where(p => p.Id==3);

      EF中提供了一个SQLServer专用的类SqlFunctions,对于EF不支持的函数提供了支持,比如:

        var result = ctx.Persons.Where(p =>SqlFunctions.DateDiff("hour",p.CreateDateTime,DateTime.Now)>1);

    十四、EF对象的状态

      1,简介
        为什么查询出来的对象Remove()、再SaveChanges()就会把数据删除。而自己new一个Person()对象,然后Remove()不行?
        为什么查询出来的对象修改属性值后、再SaveChanges()就会把数据库中的数据修改。

          因为EF会跟踪对象状态的改变。

      2,EF中中对象有五个状态:Detached(游离态,脱离态)、Unchanged(未改变)、Added(新增)、Deleted(删除)、Modified(被修改)。

      3,状态转换

        Add()、Remove()修改对象的状态。所有状态之间几乎都可以通过:Entry(p).State=xxx的方式 进行强制状态转换。

        状态改变都是依赖于Id的(Added除外)

      4,应用

        当SavaChanged()方法执行期间,会查看当前对象的EntityState的值,决定是去新增(Added)、修改(Modified)、删除(Deleted)或者什么也不做(UnChanged)。

        下面的做法不推荐,在旧版本中一些写法不被支持,到新版EF中可能也会不支持。

        ObjectStateManager
        1,不先查询再修改保存,而是直接更新部分字段的方法:

          var p = new Person();      p.Id = 2;      ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged;      p.Name = "adfad";      ctx.SaveChanges();

          也可以:

          var p = new Person();      p.Id = 5;      p.Name = "yzk";      ctx.Persons.Attach(p);//等价于ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged;      ctx.Entry(p).Property(a => a.Name).IsModified = true;      ctx.SaveChanges();

        2,不先查询再Remove再保存,而是直接根据Id删除的方法:

          var p = new Person();      p.Id = 2;      ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted;      ctx.SaveChanges();

     

        注意下面的做法并不会删除所有Name="rupeng.com" 的,因为更新、删除等都是根据Id进行的:

          var p = new Person();      p.Name = "rupeng.com";      ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted;      ctx.SaveChanges();

          上面其实是在:

            delete * from t_persons where Id=0

      5,EF优化的一个技巧

        如果查询出来的对象只是供显示使用,不会修改、删除后保存,那么可以使用AsNoTracking()来使得查询出来的对象是Detached状态,这样对对象的修改也还是Detached状态,EF不再跟踪这个对象状态的改变,能够提升性能。

        示例代码:

          原来的:

          var p1 = ctx.Persons.Where(p => p.Name == "rupeng.com").FirstOrDefault();      Console.WriteLine(ctx.Entry(p1).State);

          修改为:

          var p1 = ctx.Persons.AsNoTracking().Where(p => p.Name == "rupeng.com").FirstOrDefault();      Console.WriteLine(ctx.Entry(p1).State);

          因为AsNoTracking()是DbQuery类(DbSet的父类)的方法,所以要先在DbSet后调用AsNoTracking()。

    十五、Fluent API更多配置
      基本EF配置只要配置实体类和表、字段的对应关系、表间关联关系即可。

      如果利用EF的高级配置,可以达到更多效果:

        如果数据错误(比如字段不能为空、字符串超长等),会在EF层就会报错,而不会被提交给数据库服务器再报错;如果使用自动生成数据库,也能帮助EF生成更完美的数据库表。

      这些配置方法无论是DataAnnotations、FluentAPI都支持,下面讲FluentAPI的用法,DataAnnotations感兴趣的自己查(http://blog.csdn.net/beglorious/article/details/39637475)。

      尽量用约定,EF配置越少越好。Simple is best   参考资料:http://www.cnblogs.com/nianming/archive/2012/11/07/2757997.html

        1, HasMaxLength设定字段的最大长度

          public PersonConfig()      {        this.ToTable("T_Persons");        this.Property(p => p.Name).HasMaxLength(50);//长度为50      }

          如果插入一个Person对象,Name属性的值非常长,保存的时候就会报DbEntityValidationException异常,这个异常的Message中看不到详细的报错消息,要看EntityValidationErrors属性的值。

          var p = new Person();      p.Name = "非常长的字符串";      ctx.Persons.Add(p);      try      {        ctx.SaveChanges();      }      catch(DbEntityValidationException ex)      {        StringBuilder sb = new StringBuilder();        foreach(var ve in ex.EntityValidationErrors.SelectMany(eve=>eve.ValidationErrors))        {          sb.AppendLine(ve.PropertyName+":"+ve.ErrorMessage);        }        Console.WriteLine(sb);      }

        2, (有用)字段是否可空:

          this.Property(p => p.Name).IsRequired() //属性不能为空;      this.Property(p => p.Name).IsOptional() //属性可以为空;

          默认规则是“主键属性不允许为空,引用类型允许为空,可空的值类型long?等允许为空,值类型不允许为空。

          ”基于“尽量少配置”的原则:如果属性是值类型并且允许为null,就声明成long?等,否则声明成long等;
          如果属性属性值是引用类型,只有不允许为空的时候设置IsRequired()。
        3, 其他一般不用设置的(了解即可)
          a) 主键:this.HasKey(p => p.Id);
          b) 某个字段不参与映射数据库:this.Ignore(p => p.Name1);
          c) this.Property(p => p.Name).IsFixedLength();   //是否对应固定长度
          d) this.Property(p => p.Name).IsUnicode(false)   //对应的数据库类型是varchar类型,而不是nvarchar
          e) this.Property(p => p.Id).HasColumnName("Id");   //Id列对应数据库中名字为Id的字段
          f) this.Property(p => p.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)   //指定字段是自动增长类型。
        4,流动起来
          因为ToTable()、Property()、IsRequired()等方法的还是配置对象本身,因此可以实现类似于StringBuilder的链式编程,这就是“Fluent”一词的含义
          下面的写法可以被简化:

            public PersonConfig()        {          this.ToTable("T_Persons");          this.HasKey(p=>p.Id);          this.Ignore(p=>p.Name2);          this.Property(p=>p.Name).HasMaxLength(50);          this.Property(p=>p.Name).IsRequired();          this.Property(p=>p.CreateDateTime).HasColumnName("CreateDateTime");          this.Property(p=>p.Name).IsRequired();        }

          可以被简化为:

            public PersonConfig()        {          this.ToTable("T_Persons").HasKey(p=>p.Id).Ignore(p=>p.Name2);          this.Property(p=>p.Name).HasMaxLength(50).IsRequired();          this.Property(p=>p.CreateDateTime).HasColumnName("CreateDateTime").IsRequired();        }

    十六、一对多关系映射

      EF最有魅力的地方在于对于多表间关系的映射,可以简化工作。

      复习一下表间关系:
        1) 一对多(多对一):
          一个班级对应着多个学生,一个学生对着一个班级。一方是另外一方的唯一。
          在多端有一个指向一端的外键。
          举例:

            班级表:T_Classes(Id,Name) 学生表T_Students(Id,Name,Age,ClassId)

        2) 多对多:
          一个老师对应多个学生,一个学生对于多个老师。
          任何一方都不是对方的唯一。需要一个中间关系表。
          具体:

            学生表T_Students(Id,Name,Age,ClassId),老师表 T_Teachers(Id,Name,PhoneNum),关系表T_StudentsTeachers(Id,StudentId,TeacherId)

      和关系映射相关的方法:
        1) 基本套路this.Has****(p=>p.AAA).With***()
          当前这个表和AAA属性的表的关系是Has定义,With定义的是AAA表和这个表的关系。
        2) HasOptional()   //有一个可选的(可以为空的)

        3) HasRequired()  // 有一个必须的(不能为空的)

        4) HasMany()   //有很多的

        5) WithOptional()   //可选的

        6) WithRequired()   //必须的

        7) WithMany()   //很多的

        举例:

          在AAA实体中配置this. HasRequired(p=>p.BBB).WithMany();是什么意思?

          在AAA实体中配置this. HasRequired(p=>p.BBB). WithRequired ();是什么意思?

    十七、配置一对多关系

      1,先按照正常的单表配置把Student、Class配置起来,T_Students的ClassId字段就对应Student类的ClassId属性。WithOptional()

        using(MyDbContext ctx = new MyDbContext())    {      Class c1 = new Class{Name="三年二班"};      ctx.SaveChanges();      Student s1 = new Student{Age = 11, Name = "张三", ClassId=c1.Id};      Student s2 = new Student{Name="李四",ClassId=c1.Id};      ctx.Students.Add(s1);      ctx.Students.Add(s2);      ctx.SaveChanges();    }

      2, 给Student类增加一个Class类型、名字为Class(不一定非叫这个,但是习惯是:外键名去掉Id)的属性,要声明成virtual
      3,然后就可以实现各种对象间的操作了:
        a) Console.WriteLine(ctx.Students.First().Class.Name)
        b) 然后数据插入也变得简单了,不用再考虑“先保存Class,生成Id,再保存Student”了。这样就是纯正的“面向对象模型”,ClassId属性可以删掉。

        Class c1 = new Class { Name = "五年三班" };    ctx.Classes.Add(c1);    Student s1 = new Student { Age = 11, Name = "皮皮虾"};    Student s2 = new Student { Name = "巴斯"};    s1.Class = c1;    s2.Class = c1;    ctx.Students.Add(s1);    ctx.Students.Add(s2);    ctx.Classes.Add(c1);    ctx.SaveChanges();

      4, 如果ClassId字段可空怎么办?

          直接把ClassId属性设置为long?
      5, 还可以在Class中配置一个

        public virtual ICollection
    Students { get; set; } = new List
    ();

        属性。

        最好给这个属性初始化一个对象。注意是virtual。这样就可以获得所有指向了当前对象的Stuent集合,也就是这个班级的所有学生。

        我个人不喜欢这个属性,业界的大佬也是建议“尽量不要设计双向关系”,

        因为可以通过Class clz = ctx.Classes.First(); var students = ctx.Students.Where(s => s.ClassId == clz.Id);来查询获取到,思路更清晰。

        不过有了这样的集合属性之后一个方便的地方:

          Class c1 = new Class { Name = "五年三班" };      ctx.Classes.Add(c1);      Student s1 = new Student { Age = 11, Name = "皮皮虾" };      Student s2 = new Student { Name = "巴斯" };      c1.Students.Add(s1);//注意要在Students属性声明的时候= new List
    ();或者在之前赋值      c1.Students.Add(s2);      ctx.Classes.Add(c1);      ctx.SaveChanges();

        EF会自动追踪对象的关联关系,给那些有关联的对象也自动进行处理。

      一对多深入:

        1、 默认约定配置即可,如果非要配置,可以在StudentConfig中如下配置:

          this.HasRequired(s => s.Class).WithMany().HasForeignKey(s => s.ClassId);;

          表示“我需要(Require)一个Class,Class有很多(Many)的Student;ClassId是这样一个外键”。

          如果ClassId可空,那么就要写成:this. HasOptional (s => s.Class).WithMany().HasForeignKey(s => s.ClassId);

        2、 一对多的关系在一端配置就可以了,当然两边都配也不错。思考:如果把一对多的关系配置到ClassConfig中(不建议这么搞)怎么配?

        3、 如果一张表中有两个指向另外一个表的外键怎么办?比如学生有“正常班级Class”(不能空)和“小灶班级XZClass”(可以空)两个班。如果用默认约定就会报错,怎么办?

          this.HasRequired(s => s.Class).WithMany().HasForeignKey(s => s.ClassId);      this. HasOptional (s => s.XZClass).WithMany().HasForeignKey(s => s.XZClassId);

    十八、多对多关系配置
        老师和学生:

        class Student    {      public long Id { set; get; }      public string Name { get; set; }      public virtual ICollection
    Teachers { get; set; }=new List
    ();    }    class Teacher    {      public long Id { set; get; }      public string Name { get; set; }      public virtual ICollection
    Students { get; set; }=new List< Student >();    }
        class StudentConfig : EntityTypeConfiguration
        {      public StudentConfig()      {        ToTable("T_Students");      }    }
        class TeacherConfig : EntityTypeConfiguration
        {      public TeacherConfig()      {        ToTable("T_Teachers");        this.HasMany(e => e.Students).WithMany(e => e.Teachers).Map(m => m.ToTable("T_TeacherStudentRelations").MapLeftKey("TeacherId").MapRightKey("StudentId"));      }    }

        这样不用中间表建实体(也可以为中间表建立一个实体,其实思路更清晰),就可以完成多对多映射。当然如果中间关系表还想有其他字段,则要必须为中间表建立实体类。

        测试:

          Teacher t1 = new Teacher();      t1.Name = "张老师";      t1.Students = new List
    ();      Teacher t2 = new Teacher();      t2.Name = "王老师";      t2.Students = new List
    ();      Student s1 = new Student();      s1.Name = "tom";      s1.Teachers = new List
    ();       Student s2 = new Student();      s2.Name = "jerry";      s2.Teachers = new List
    ();      t1.Students.Add(s1);

          把中间表也建成一个实体了。

          t1.Students.Add(s2);      s1.Teachers.Add(t1);      s2.Teachers.Add(t1);      ctx.Students.Add(s1);      ctx.Students.Add(s2);      ctx.SaveChanges();

    十九、延迟加载(LazyLoad)

        如果public virtual Class Class { get; set; }把virtual去掉,那么下面的代码就会报空引用异常

          var s = ctx.Students.First();      Console.WriteLine(s.Class.Name);

        强调:如果要使用延迟加载,类必须是public,关联属性必须是virtual。

      延迟加载(LazyLoad)的优点:
        用到的时候才加载,没用到的时候才加载,因此避免了一次性加载所有数据,提高了加载的速度。
      缺点:
        如果不用延迟加载,就可以一次数据库查询就可以把所有数据都取出来(使用join实现),用了延迟加载就要多次执行数据库操作,提高了数据库服务器的压力。
      因此:如果关联的属性几乎都要读取到,那么就不要用延迟加载;如果关联的属性只有较小的概率(比如年龄大于7岁的学生显示班级名字,否则就不显示)则可以启用延迟加载。
    这个概率到底是多少是没有一个固定的值,和数据、业务、技术架构的特点都有关系,这是需要经验和直觉,也需要测试和平衡的。
    注意:启用延迟加载的时候拿到的对象是动态生成类的对象,是不可序列化的,因此不能直接放到进程外Session、Redis等中,要转换成DTO(后面讲)再保存。
    二十、不延迟加载,怎么样一次性加载

      使用Include()方法:

        var s = ctx.Students.Include("Class").First();//

        观察生成的SQL语句,会发现只执行一个使用join的SQL就把所有用到的数据取出来了。当然拿到的对象还是Student的子类对象,但是不会延迟加载。(不用研究“怎么让他返回Student对象”)

        Include("Class")的意思是直接加载Student的Class属性的数据。注意只有关联的对象属性才可以用Include,普通字段不可以

        直接写"Class"可能拼写错误,如果用C#6.0,可以使用nameof语法解决问这个问题:

          var s = ctx.Students.Include(nameof(Student.Class)).First();

        也可以using System.Data.Entity;然后var s = ctx.Students.Include(e=>e.Class).First();   推荐这种做法。

        如果有多个属性需要一次性加载,也可以写多个Include:

          var s = ctx.Students.Include(e=>e.Class) .Include(e=>e.Teacher).First();

        如果Class对象还有一个School属性,也想把School对象的属性也加载,就要:

          var s = ctx.Students.Include("Class").Include("Class. School").First();

        或者更好的

          var s = ctx.Students.Include(nameof(Student.Class))

    二十一、延迟加载的一些坑

      1,DbContext销毁后就不能再延迟加载了,因为数据库连接已经断开

        下面的代码最后一行会报错:

        Student s;    using (MyDbContext ctx = new MyDbContext())    {      s = ctx.Students.First();    }    Console.WriteLine(s.Class.Name);

        两种解决方法:

        1,用Include,不延迟加载(推荐)

          Student s;      using (MyDbContext ctx = new MyDbContext())      {        s = ctx.Students.Include(t=>t.Class).First();      }      Console.WriteLine(s.Class.Name);

        2,关闭前把要用到的数据取出来

          Class c;      using (MyDbContext ctx = new MyDbContext())      {        Student s = ctx.Students.Include(t=>t.Class).First();        c = s.Class;      }      Console.WriteLine(c.Name);

      2,两个取数一起使用

        下面的程序会报错:
          已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。

          foreach(var s in ctx.Students)      {        Console.WriteLine(s.Name);        Console.WriteLine(s.Class.Name);      }

          因为EF的查询是“延迟执行”的,只有遍历结果集的时候才执行select查询,而由于延迟加载的存在到s.Class.Name也会再次执行查询。ADO.Net中默认是不能同时遍历两个DataReader。因此就报错。

        三种解决方式:
          1,允许多个DataReader一起执行:

            在连接字符串上加上MultipleActiveResultSets=true,但只适用于SQL 2005以后的版本。其他数据库不支持。

          2,执行一下ToList(),因为ToList()就遍历然后生成List:

            foreach(var s in ctx.Students.ToList())        {          Console.WriteLine(s.Name);          Console.WriteLine(s.Class.Name);        }

          3,推荐做法:用Include预先加载:

            foreach(var s in ctx.Students.Include(e=>e.Class))        {          Console.WriteLine(s.Name);          Console.WriteLine(s.Class.Name);        }

    二十二、实体类的继承

      所有实体类都会有一些公共属性,可以把这些属性定义到一个父类中。比如:

        public abstract class BaseEntity    {      public long Id { get; set; } //主键      public bool IsDeleted { get; set; } = false; //软删除      public DateTime CreateDateTime { get; set; } = DateTime.Now;//创建时间      public DateTime DeleteDateTime { get; set; } //删除时间    }

      使用公共父类的好处不仅是写实体类简单了,而且可以提供一个公共的Entity操作类:

        class BaseDAO
    where T:BaseEntity    {      private MyDbContext ctx;//不自己维护MyDbContext而是由调用者传递,因为调用者可以要执行很多操作,由调用者决定什么时候销毁。      public BaseDAO (MyDbContext ctx)      {        this.ctx = ctx;      }      public IQueryable
    GetAll()//获得所有数据(不要软删除的)      {        return ctx.Set
    ().Where(t=>t.IsDeleted==false);//这样自动处理软删除,避免了忘了过滤软删除的数据      }      public IQueryable
    GetAll(int start,int count) //分页获得所有数据(不要软删除的)      {        return GetAll().Skip(start).Take(count);      }      public long GetTotalCount()//获取所有数据的条数      {        return GetAll().LongCount();      }      public T GetById(long id)//根据id获取      {        return GetAll().Where(t=>t.Id==id).SingleOrDefault();      }      public void MarkDeleted(long id)//软删除      {        T en = GetById(id);        if(en!=null)        {          en.IsDeleted = true;          en.DeleteDateTime = DateTime.Now;          ctx.SaveChanges();        }      }    }

        下面的代码会报错:

        using (MyDbContext ctx = new MyDbContext())    {      BaseDAO
    dao = new BaseDAO
    (ctx);      foreach(var s in dao.GetAll())      {        Console.WriteLine(s.Name);        Console.WriteLine(s.Class.Name);      }    }

        原因是什么?

        怎么Include?需要using System.Data.Entity;

          using (MyDbContext ctx = new MyDbContext())      {        BaseDAO
    dao = new BaseDAO
    (ctx);        foreach(var s in dao.GetAll().Include(t=>t.Class))        {          Console.WriteLine(s.Name);          Console.WriteLine(s.Class.Name);        }      }

        有两个版本的Include、AsNoTracking:

          1) DbQuery中的:

            DbQuery<TResult> AsNoTracking()、

            DbQuery<TResult> Include(string path)

          2) QueryableExtensions中的扩展方法:
            AsNoTracking<T>(this IQueryable<T> source) 、

            Include<T>(this IQueryable<T> source, string path)、

            Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path)

          DbSet继承自DbQuery;Where()、Order、Skip()等这些方法返回的是IQueryable接口。因此如果在IQueryable接口类型的对象上调用Include、AsNoTracking就要using System.Data.Entity
    二十三、其他
        还有其他优秀的ORM框架:NHibernate、Dapper、PetaPoco、IBatis.Net;

     

     

    ASP.Net MVC+Entity Framework的架构

    一、了解一些不推荐的做法
      有的项目里是直接把EF代码写到ASP.Net MVC的Controller中,这样做其实不符合分层的原则。ASP.Net MVC是UI层的框架,EF是数据访问的逻辑。
      如果就要这么做怎么做的呢?
      如果在Controller中using DbContext,把查询的结果的对象放到cshtml中显示,那么一旦在cshtml中访问关联属性,那么就会报错。因为关联属性可以一直关联下去,很诱惑人,include也来不及。
      如果不using也没问题,因为会自动回收。但是这是打开了“潘多拉魔盒”,甚至可以在UI层更新数据。相当于把数据逻辑写到了UI层。
      有的三层架构中用实体类做Model,这样也是不好的,因为实体类属于DAL层的逻辑。
    二、EO、DTO、ViewModel
      EO(Entity Object,实体对象)就是EF中的实体类,对EO的操作会对数据库产生影响。EO不应该传递到其他层。

      DTO(Data Transfer Object,数据传输对象),用于在各个层之间传递数据的普通类。

        DTO有哪些属性取决于其他层要什么数据。DTO一般是“扁平类”,也就是没有关联属性,都是普通类型属性。
        一些复杂项目中,数据访问层(DAL)和业务逻辑层(BLL)直接传递用一个DTO类,UI层和BLL层之间用一个新的DTO类。简单的项目共用同一个DTO。DTO类似于三层架构中的Model。

      ViewModel(视图模型),用来组合来自其他层的数据显示到UI层。简单的数据可能可以直接把DTO交给界面显示,一些复杂的数据可以要从新转换为ViewModel对象。

    三、多层架构

      搭建一个ASP.Net 三层架构项目:DAL、BLL、DTO、UI(asp.net mvc)。

      UI、DAL、BLL都引用DTO;BLL引用DAL;EF中的所有代码都定义到DAL中,BLL中只访问DTO、BLL中不要引用DAL中的EF相关的类、不要在BLL中执行Include等操作、所有数据的准备工作都在DAL中完成。

      注意:.Net中配置文件都是加载UI项目(ASP.net MVC)的,而不是加载DAL中的配置文件,因此EF的配置、连接字符串应该挪到UI项目中。

    没有“正确的架构”,“错误的架构”,

      只有“合适的架构” : 能够满足当前项目的要求,并且适当的考虑以后项目的发展,不要想的“太远”,不要“过度架构”;让新手能够非常快的上手

      CRUD例子,带关联关系。班级管理、学生管理、民族
      UI项目虽然不直接访问EF中的类,但是仍然需要在UI项目的App.config(Web.config)中对EF做配置,也要在项目中通过Nuget安装EF,并且要把连接字符串也配置到UI项目的App.config(Web.config)中

    转载于:https://www.cnblogs.com/DotNetStu/p/7412404.html

    你可能感兴趣的文章
    MySQL的基本使用命令
    查看>>
    output 参数在存储过程中的用法
    查看>>
    大数加法和乘法(高精度)
    查看>>
    利用SynchronizationContext.Current在线程间同步上下文
    查看>>
    python各种类型转换-int,str,char,float,ord,hex,oct等
    查看>>
    sublime Text3 快捷键
    查看>>
    19 年书单
    查看>>
    不变模式
    查看>>
    matlab去云雾
    查看>>
    500lines项目简介
    查看>>
    Asp.net core logging 日志
    查看>>
    BOM浏览器对象模型
    查看>>
    Jq 遍历each()方法
    查看>>
    Android源码分析:Telephony部分–phone进程
    查看>>
    关于 redis.properties配置文件及rule
    查看>>
    WebService
    查看>>
    关于Java中重载的若干问题
    查看>>
    Java中start和run方法的区别
    查看>>
    23种设计模式中的命令模式
    查看>>
    [转载]年薪10w和年薪100w的人,差在哪里?
    查看>>