一、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 <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> <input type="text" value="@Model.Num1" />+<input type="text" value="@Model.Num2" />=@Model.Result </div> </body> </html>
	  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) { <span>文字</span> }
     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属性。
		    但是对于特殊的邮箱或者就是要显示@,那么可以使用@转义@,也就是“@@”
<li>item_@item.Length</span>//会把@item.Length识别成邮箱, 因此用上()成为: <li>item_@(item.Length)</span>
   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; } <input type="checkbox" checked="@b1"/>//此时生成的html代码为:<input type="checkbox" checked="checked">
     这个特性避免了进行三元运算符的判断
	
	  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了
		
示例代码:
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="Test1" /> </namespaces> </pages> </system.web.webPages.razor>
   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判断哪项应该处于选中状态
			
<select> @foreach(var p in (IEnumerable<Person>)ViewBag.list) { <option selected="@(p.Id==3)">@p.Name</option> } </select>
     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<Person> list = new List<Person>(); 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<SelectListItem> sliList = new List<SelectListItem>(); 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<SelectListItem> <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>DDL</title> </head> <body> <div> @Html.DropDownList("pid", Model); </div> </body> </html>
     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源码为:
<select aaa="rupeng" class="warn error" id="yzk" name="pid" onchange="javascript:alert('ok')" style="color:red">
       支持自定义属性,给你原样输出,具体什么含义自己定;
			      由于class是关键字,所以不能直接用class="",要加上一个@前缀,这其实是C#中给变量名取名为关键字的一种语法;
			      注意:
				        id默认和name一致,如果设定了id则覆盖默认的实现。
		
		    3)构造一个特殊的集合类SelectList,他会自动帮着做集合的遍历
			
public ActionResult DDL2() { List<Person> list = new List<Person>(); 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<int> a1 = delegate(int i){Console.writeLine(i);};
2,可以简化为(=>读作goes to)
Action<int> a2 = (int i)=>{Console.writeLine(i);};
3,还可以省略参数类型(编译器会自动根据委托类型解析):
      Action<int> a3 =(i)=>{Console.writeLine(i);};
4,如果只有一个参数还可以省略参数的小括号(多个参数不行)
      Action<int> a4 = i=>{Console.writeLine(i);};
     5,如果委托有返回值,并且方法体只有一行代码,这一行代码还是返回值,那么就可以连方法的大括号和return都省略:
			
Func<int,int,string> f1 = delegate(int i,int j){return "结果是"+(i+j);};//原始形式 Func<int,int,string>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<Person>(); } public string Name { get; set; } public List<Person> 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<Person> list = new List<Person>(); 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中配置连接字符串
<add name="conn1" connectionString="Data Source=.;Initial Catalog=test1;User ID=sa;Password=msn@qq888" providerName="System.Data.SqlClient" />
七、 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<Person> 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<Person> { 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<Person> 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。这种方式问题最少,虽然性能略低,但是caozuo.html" target="_blank">删除操作一般不频繁,不用考虑性能。后续在“状态管理”中会讲其他实现方法
		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<Person>,如果写成IEnumerable就会在内存中取后续数据
	IQueryable<Person> items = ctx.Persons;//为什么把IQueryable<Person>换成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<XXXDbContext>(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<Item1>("select Name,Count(*) Count from T_Persons where Id>{0} and CreateDateTime<={1} group by Name",2, DateTime.Now); //返回值是DbRawSqlQuery<T> 类型,也是实现了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<int>("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<Student> Students { get; set; } = new List<Student>(); 属性。
		最好给这个属性初始化一个对象。注意是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<Student>();或者在之前赋值
			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<Teacher> Teachers { get; set; }=new List<Teacher>();
		}
		
		class Teacher
		{
			public long Id { set; get; }
			public string Name { get; set; }
			public virtual ICollection<Student> Students { get; set; }=new List< Student >();
		}
		
		class StudentConfig : EntityTypeConfiguration<Student>
		{
			public StudentConfig()
			{
				ToTable("T_Students");
			}
		}
		class TeacherConfig : EntityTypeConfiguration<Teacher>
		{
			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<Student>();
		Teacher t2 = new Teacher();
		t2.Name = "王老师";
		t2.Students = new List<Student>();
		Student s1 = new Student();
		s1.Name = "tom";
		s1.Teachers = new List<Teacher>();	
	
		Student s2 = new Student();
		s2.Name = "jerry";
		s2.Teachers = new List<Teacher>();
	
		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<T> where T:BaseEntity
	[
		private MyDbContext ctx;//不自己维护MyDbContext而是由调用者传递,因为调用者可以要执行很多操作,由调用者决定什么时候销毁。
		public BaseDAO (MyDbContext ctx)
		{
			this.ctx = ctx;
		}
		public IQueryable<T> GetAll()//获得所有数据(不要软删除的)
		{
			return ctx.Set<T>().Where(t=>t.IsDeleted==false);//这样自动处理软删除,避免了忘了过滤软删除的数据
		}
		public IQueryable<T> 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<Student> dao = new BaseDAO<Student>(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<Student> dao = new BaseDAO<Student>(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)中