本篇原文链接: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

【EF6学习笔记】(十二)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. EF6 学习笔记(二):操练 CRUD 增删改查

    EF6学习笔记总目录 ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 接上篇: EF6 学习笔记(一):Code First 方式生成数据库及初始化数据库实际操作 本篇原文链接: I ...

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

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

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

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

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

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

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

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

  8. JavaScript权威设计--命名空间,函数,闭包(简要学习笔记十二)

    1.作为命名空间的函数 有时候我们需要声明很多变量.这样的变量会污染全局变量并且可能与别人声明的变量产生冲突. 这时.解决办法是将代码放入一个函数中,然后调用这个函数.这样全局变量就变成了 局部变量. ...

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

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

  10. MySQL学习笔记十二:数据备份与恢复

    数据备份 1.物理备份与逻辑备份 物理备份 物理备份就是将数据库的数据文件,配置文件,日志文件等复制一份到其他路径上,这种备份速度一般较快,因为只有I/O操作.进行物理备份时,一般都需要关闭mysql ...

随机推荐

  1. arduino 引脚作为输入时的不稳定 解决方案

    问题描述: arduino引脚作为输入状态时,高低电平不稳定 出现的原因: arduino 引脚为输入时,引脚电平处于悬空状态,容易受外部电荷信号等干扰 解决的方案: 再程序配置为输入状态后 使用下拉 ...

  2. 安装 redis 拓展

    PHP API 20121113 PHP Extension 20121212 Zend Extension 220121212 Zend Extension Build API220121212,T ...

  3. shiro简单配置 (写的不错 收藏一下)

    抄袭的连接:https://blog.csdn.net/clj198606061111/article/details/24185023 注:这里只介绍spring配置模式. 因为官方例子虽然中有更加 ...

  4. String常用类

    一.String类String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象.java把String类声明的final类,不能有类.String类对象创建 ...

  5. Notes on Operating System

  6. Git使用(二、分支的创建和上传)

    介绍使用TortoiseGit创建分支并push到gitlab项目库,转载请注明出处. 一.创建一个新的文件夹,把要待编辑的工程从gitlab上pull到该文件夹. 其中URL从gitlab的对应项目 ...

  7. Android从入门到进阶——布局

    一.组件 1.UI组件 (Android.view.View的子类或者间接子类) 2.容器组件(Android.view.ViewGroup子类或者间接子类) 二.UI组件:TextView,Spin ...

  8. Tensoflow API笔记(N) 设备指定

        tf.device是tf.Graph.device()的一个包装,是一个用于指定新创建的操作(operation)的默认设备的环境管理器.参数为device_name_or_function, ...

  9. CSS3变换、过渡、动画效果

    为元素创建圆角 1.四个相同的圆角 -webkit-border-radius:10px; border-radius:10px; 2.一个圆角 -webkit-border-top-left-rad ...

  10. 乘法器的Verilog HDL实现(转载)

    原文地址:http://www.cnblogs.com/shengansong/archive/2011/05/23/2054401.html 1. 串行乘法器 两个N位二进制数x.y的乘积用简单的方 ...