学习总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理)

上篇链接:EF学习笔记(十一):实施继承

本篇原文链接:Advanced Entity Framework Scenarios

本篇主要讲一些使用Code First建立ASP.NET WEB应用的时候除了基础的方式以外的一些扩展方式方法:

1、Performing Raw SQL Queries (执行真正的SQL语句)

2、Performing no-tracking queries (执行无跟踪的SQL语句)

3、Examining SQL sent to the database (检查发到数据库的SQL语句)

Performing Raw SQL Queries

EF Code First 有API 可以让用户直接把SQL语句发给数据库去执行,有以下几种选择:

1、DbSet.SqlQuery  执行查询语句后返回实体类型(即DbSet对象所预期的);同时被数据库上下文自动跟踪(除非手动关闭,可参看AsNoTracking 方法)

2、Database.SqlQuery 可以用来执行查询语句后返回不是实体类型的,同时也不会被数据库上下文跟踪,即便是返回的是实体类型;

3、Database.ExecuteSqlCommand 执行非查询的SQL语句

用EF的一个好处就是一些重复的语句可以不用自己再写,它可以为你生成一些SQL语句,可以解放你不用自己再写;

但是也有一些场景,需要你自己运行自己手动创建的SQL或者方法来处理一些特殊异常情况。

在WEB网页应用的时候,必须预防SQL注入攻击。最好的办法就是用参数化查询语句,而不是拼接字符串。

Calling a Query that Returns Entities

DbSet<TEntity>类提供了一个方法可以用来执行一个SQL语句,并返回一个实体类型。

简单的例子:

        public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
} //Department department = await db.Departments.FindAsync(id); // Create and execute raw SQL query.
string query = "SELECT * FROM Department WHERE DepartmentID = @p0";
Department department = await db.Departments.SqlQuery(query, id).SingleOrDefaultAsync(); if (department == null)
{
return HttpNotFound();
}
return View(department);
}

Calling a Query that Returns Other Types of Objects

在前面一个章节设计了Home/About 里面的代码:

        public ActionResult About()
{
IQueryable<EnrollmentDateGroup> data = from student in db.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(data.ToList());
}

可以替换为:

        public ActionResult About()
{
//IQueryable<EnrollmentDateGroup> data = from student in db.Students
// group student by student.EnrollmentDate into dateGroup
// select new EnrollmentDateGroup()
// {
// EnrollmentDate = dateGroup.Key,
// StudentCount = dateGroup.Count()
// }; string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
IEnumerable<EnrollmentDateGroup> data = db.Database.SqlQuery<EnrollmentDateGroup>(query);
return View(data.ToList());
}

运行起来,效果是一样的。

Calling an Update Query

假设需要一个页面用来批量调整 Course的 Credits;

在Course控制器增加两个Action:

public ActionResult UpdateCourseCredits()
{
return View();
} [HttpPost]
public ActionResult UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewBag.RowsAffected = db.Database.ExecuteSqlCommand("UPDATE Course SET Credits = Credits * {0}", multiplier);
}
return View();
}

当Get UpdateCourseCredits请求的时候,就给用户一个编辑框和一个提交按钮;
当用户输入后,点击提交后,返回带有更新了多少条记录的显示信息的页面;

为UpdateCourseCredits建一个空的视图,然后用下面代码代替:

@model EFTest.Models.Course

@{
ViewBag.Title = "UpdateCourseCredits";
} <h2>Update Course Credits</h2> @if (ViewBag.RowsAffected == null)
{
using (Html.BeginForm())
{
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" />
</p>
}
}
@if (ViewBag.RowsAffected != null)
{
<p>
Number of rows updated: @ViewBag.RowsAffected
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>

把应用执行起来后,进入Course页面,然后在后面加UpdateCourseCredits

访问如 http://XXXXXXX/Course/UpdateCourseCredits 这个URL:

然后输入2后,点Update:

会显示一共多少记录被更新;然后返回Course主页面看到所有的都乘以2了:

更多关于执行原生SQL语句的内容见:  Raw SQL Queries

No-Tracking Queries

当一个数据库上下文检索数据表行并建立一个实体对象用来表示它的时候,默认是跟踪在内存里的实体和在数据库里的保持同步。

在内存里的数据就像缓存用来更新实体数据。而缓存在WEB应用中不是必须的,因为上下文实例是典型的短生命周期的,每次请求会新建一个,用完就disposed,在实体数据被再使用的时候,上一个上下文实例已经被disposed了。

所以可以用AsNoTracking方法来不跟踪内存里的实体对象。

典型的场景包括:

1、一次查询很大量的数据,如果要跟踪,就是极大的降低性能;

2、当准备附加一个实体计划更新时,如果因为其他原因,已经检索同一个实体,而因为该实体被数据库上下文所跟踪,所以你就不可以附加这个实体。唯一的办法就是在之前的检索中采用AsNoTracking方式。

这里原文说到以前的教程中做了这个测试,并提供了链接;

我在这里按照以前的教程做这个测试:

先为Department 控制器加一个私有方法来检查数据库,是不是这个Administrator已经对应了一个Department,如果已经对应,则直接提示错误;

private void ValidateOneAdministratorAssignmentPerInstructor(Department department)
{
if (department.PersonID != null)
{
var duplicateDepartment = db.Departments
.Include("Administrator")
.Where(d => d.PersonID == department.PersonID)
.FirstOrDefault();
if (duplicateDepartment != null && duplicateDepartment.DepartmentID != department.DepartmentID)
{
var errorMessage = String.Format(
"Instructor {0} {1} is already administrator of the {2} department.",
duplicateDepartment.Administrator.FirstMidName,
duplicateDepartment.Administrator.LastName,
duplicateDepartment.Name);
ModelState.AddModelError(string.Empty, errorMessage);
}
}
}

把Edit HttpPost Action 改成以下简单的方式,原先检查并发的先注释掉:

        [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "DepartmentID, Name, Budget, StartDate, RowVersion, InstructorID")] Department department)
{
try
{
if (ModelState.IsValid)
{
ValidateOneAdministratorAssignmentPerInstructor(department);
} if (ModelState.IsValid)
{
db.Entry(department).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Department)entry.Entity;
}
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", department.InstructorID);
return View(department);
}

下面进行测试:先编辑一个Department,然后把Administrator选为和另一个Department一样,这个时候就不会继续执行SaveChange的操作了,而是直接报错:

如果返回到部门主页,再点击编辑,这次随便编辑其他值,比如Budget :

就会出现错误:

就是因为附件这个实体的时候,前面检查Administrator的时候已经去数据库查询所有Administrator 为 Kapoor, Candace 的Dempartment,导致Economics部门已经被读取,并已经跟踪;

当Edit Action尝试为 MVC自动绑定的Department 实体 更改标识的时候,会导致这个错误,因为这个Department 已经被读取,并被跟踪。

要解决这个问题,也很简单,在ValidateOneAdministratorAssignmentPerInstructor 方法中,加入AsNoTracking(),如下:

var duplicateDepartment = db.Departments
.Include("Administrator")
.Where(d => d.PersonID == department.PersonID)
.AsNoTracking()
.FirstOrDefault();

这样改好就没有问题了。

Examining SQL sent to the database

有时候需要能够看到实际发到数据库的SQL语句,对于调试程序有很大帮助,前面的教程讲了采用拦截的方式,把一些信息拦截到Output去查看;

下面准备说一个简单的方案来查看实际发到数据库的SQL语句:

把Course的Index Action变为以下简单的方式:

        public ActionResult Index()
{ var courses = db.Courses;
var sql = courses.ToString();
return View(courses.ToList()); //var courses = db.Courses.Include(c => c.Department);
//return View(courses.ToList());
}

然后在 return语句加上断点: 选中行点F9即可:

然后按F5执行应用,点击进入Course\Index页面,会停留在这一行:

然后光标停留在上一行sql上面,就可以看到sql 的值:

点那个放大镜图标,可以看的详细点:

下面为Course Index页面加一个Department 下拉式的过滤;

修改Course Index Action 为:

public ActionResult Index(int? SelectedDepartment)
{
var departments = db.Departments.OrderBy(q => q.Name).ToList();
ViewBag.SelectedDepartment = new SelectList(departments, "DepartmentID", "Name", SelectedDepartment);
int departmentID = SelectedDepartment.GetValueOrDefault(); IQueryable<Course> courses = db.Courses
.Where(c => !SelectedDepartment.HasValue || c.DepartmentID == departmentID)
.OrderBy(d => d.CourseID)
.Include(d => d.Department);
var sql = courses.ToString();
return View(courses.ToList());
}

再在Index视图 Table元素前面加上:

@using (Html.BeginForm())
{
<p>Select Department: @Html.DropDownList("SelectedDepartment","All")
<input type="submit" value="Filter" /></p>
}

效果如图:

可以在return语句加上断点:

这个时候运行到这里的时候,就可以看sql的值:(加入了Department的查询语句)

Repository and unit of work patterns

仓库和单元工作模式是很多开发者会去写代码实现的,在数据访问层和业务逻辑层之间加入一个虚拟层;

这种模式可以帮助应用与数据存储变化隔离开,可促进实现单元测试或者TDD test-driven development;

然而,在使用EF的情况下,再额外写代码实现这个模式已经不是最好的方式了,主要是以下原因:

1、EF 的上下文类已经实现应用代码和数据交互部分的隔离;

2、EF的上下文类可以作为单元工作类来进行数据库更新;

3、EF6 可以让这种模式实现起来更简单,而不用再自己写仓库代码;

更多学习仓库和单元工作模式,可以参考:the Entity Framework 5 version of this tutorial series.

EF6如何实现TDD,可以参考:

How EF6 Enables Mocking DbSets more easily

Testing with a mocking framework

Testing with your own test doubles

Proxy classes 代理类

当EF创建一个实体实例的时候,它一般会为这个实体动态生成一个衍生类型作为代理,然后再创建这个代理的实例;

可以看原文中的两个图,第1个图,可以看到Student申明的时候是Student 类型,但到第2步,从数据库读取数据后,就可以看到代理类:

代理类重载了一些虚拟导航属性,用一些执行动作的钩子来填充,当这些虚拟属性被访问的时候,可以自动执行;这种机制主要是为了 延时加载;

大部分情况下,不需要关注代理类的工作,但是以下情况是特例:

1、在一些场景下,需要阻止EF产生代理类,比如你准备序列化实体的时候,肯定是希望是POCO类,而不是代理类;有一种办法来解决这个序列化问题,就是用序列化DTO而不是序列化实体对象;如Using Web API with Entity Framework;(一般在WEB API里序列化实体要求比较多); 另外也可以直接关闭代理类:disable proxy creation.

2、当直接用new来实例化一个实体的时候,你得到的不是代理类,这以为着你得不到延时加载、数据变化跟踪这些功能;这个应该也不是大问题,毕竟这个不是从数据库里取的数据,通常不需要延时加载,如果你明确定义它为Added ,你也不需要数据跟踪。 然而,你如果需要延时加载、数据变化跟踪,可以用DbSet的Create方法来新建代理类;

3、如果你需要从一个代理类实例获取真正的实体类型,可以使用ObjectContextGetObjectType方法从一个代理类实例获取实体类型;

更多代理类信息参考:Working with Proxies

Automatic change detection 自动变化侦测

EF通过比较当前值和原始值来确定哪些实体变化了(因而要更新到数据库里),原始值是在被检索或者附加的时候存储,一些方法引发自动变化侦测: 

  • DbSet.Find
  • DbSet.Local
  • DbSet.Remove
  • DbSet.Add
  • DbSet.Attach
  • DbContext.SaveChanges
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries

如果在跟踪很多实体,并且在一个循环操作中,要Call上面这些方法很多次的话,那么临时性关闭数据变化侦测(通过使用AutoDetectChangesEnabled这个属性值)是可以带来很大的性能提升的;

更多信息见:Automatically Detecting Changes

 Automatic validation 自动数据校验

EF默认会在SaveChange的时候校验数据,而如果是很大量的数据,而且已经被校验过了,那么可以通过临时性关闭自动校验(通过使用ValidateOnSaveEnabled这个属性值)来提高性能;

更多信息见: Validation

EF学习笔记(十二):EF高级应用场景的更多相关文章

  1. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  2. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  3. java jvm学习笔记十二(访问控制器的栈校验机制)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...

  4. (C/C++学习笔记) 十二. 指针

    十二. 指针 ● 基本概念 位系统下为4字节(8位十六进制数),在64位系统下为8字节(16位十六进制数) 进制表示的, 内存地址不占用内存空间 指针本身是一种数据类型, 它可以指向int, char ...

  5. Python学习笔记(十二)—Python3中pip包管理工具的安装【转】

    本文转载自:https://blog.csdn.net/sinat_14849739/article/details/79101529 版权声明:本文为博主原创文章,未经博主允许不得转载. https ...

  6. EF学习笔记(二)

    DbContext 1.指定连接字符串(上一章提到) public string ConnectionStringName { get; private set; } /// <summary& ...

  7. Oracle学习笔记十二 子程序(存储过程、自定函数)和程序包

    子程序 子程序:命名的 PL/SQL 块,编译并存储在数据库中.   子程序的各个部分: 1.声明部分 2.可执行部分 3.异常处理部分(可选) 子程序的分类: 1.过程 - 执行某些操作 2.函数 ...

  8. python 学习笔记十二 CSS基础(进阶篇)

    1.CSS 简介 CSS 指层叠样式表 (Cascading Style Sheets) 样式定义如何显示 HTML 元素 样式通常存储在样式表中 把样式添加到 HTML 4.0 中,是为了解决内容与 ...

  9. 【EF学习笔记04】----------EF简单增删改查

    第一步:创建上下文对象 using(var db = new Entities()) { //数据操作 } 新增 UserInfo user = new UserInfo() { UserName = ...

  10. ROS学习笔记十二:使用gazebo在ROS中仿真

    想要在ROS系统中对我们的机器人进行仿真,需要使用gazebo. gazebo是一种适用于复杂室内多机器人和室外环境的仿真环境.它能够在三维环境中对多个机器人.传感器及物体进行仿真,产生实际传感器反馈 ...

随机推荐

  1. 一.javascript核心部分:1.词法结构

    本文作为个人学习笔记,一直也没有重视javascript的系统学习(javascript是最容易被人忽视的语言),我都是要用的时候百度一下查找下资料开始用,但没有系统的,学习,和整理过javascri ...

  2. Zabbix 各种报错信息和遇到的问题处理(持续总结更新~~~~~)

    问题1:Zabbix poller processes more than 75% busy 解决: 1.修改配置文件: # vim /etc/zabbix/zabbix_server.conf St ...

  3. 手游开发之lua的class函数详解

    众所周知,lua没有类这个概念但其通过table实现了面向对象的“类”.在cocos2dx引擎下提供了class(className, ...)函数方法名,因为在脚本开发中这个接口基本都会用来创建一个 ...

  4. appium 版本更新后的方法变化更新收集 ---持续更新

    在高版本的android手机(例如android 7.0 , 8.0等等),必须使用高版本的appium, 以及对应的selenium版本,那么很多的appium或者selenium方法会变得无法直接 ...

  5. JMeter之自动重定向和跟随重定向的区别

    自动重定向:只针对Get和Head请求,自动重定向转向到最终目标页面,但是Jmeter不记录重定向的中间页面过程,只记录最终页面返回结果.在结果树中,只能看到最终页面的服务器返回. 跟随重定向:是ht ...

  6. python note 08 文件操作

    1.相对路径与绝对路径比较 #绝对路径 f = open('d:\pzw.txt',mode='r',encoding='UTF-8') content = f.read() print(conten ...

  7. Python:a,*args,**kwargs的理解

    1.何时用这些参数? 在任何时候继承类和重写方法时,应当用到’*args’和’**kwargs’将接收到的位置参数和键值参数给父类方法 . 2.一句话清晰说明: a是常规的变量类型,比如int,str ...

  8. WPF中的数据绑定(初级)

    关于WPF中的数据绑定,初步探讨 数据绑定属于WPF中比较核心的范畴,以下是对WPF中数据绑定的一个初步探讨.个人感觉还是带有问题性质的叙述比较高效,也比较容易懂 第一,什么是数据绑定? 假定有这么一 ...

  9. JavaScript 教程

    JavaScript 教程:https://code.ziqiangxuetang.com/js/js-tutorial.html

  10. XML文件的解析—DOM、SAX

    一.DOM 解析 思路:获得Document对象,遍历其中节点获得需要的内容 要点: Document :  DocuemntBuilderFactory --newDocumentBuilder - ...