原文:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application

1.执行原生SQL查询

  EF Code First API包含直接传递SQL命令到数据库的方法:

  • 使用DbSet.SqlQuery方法查询返回实体类型。返回的对象必须是DbSet对象所期望的类型,并且数据库上下文会自动追踪它们除非我们关闭了追踪。
  • 使用Database.SqlQuery方法查询返回非实体类型。返回的数据不会被数据库上下文追踪,即使我们为该方法指定实体类型。
  • 使用Database.ExecuteSqlCommand执行非查询操作。

  使用EF的其中一个优点是避免了将我们的代码与特定的存储数据的方法联系的过于紧密。该优点通过为我们产生SQL查询和命令实现,这同时可以让我们不必自己编写这些语句。但在特殊情况下,我们需要执行自己编写的SQL语句,并且这些方法是我们能够处理这些异常。

  长久以来当我们在web应用程序中执行SQL命令时,我们必须采取措施来防止SQL注入攻击 。一种方法是使用参数化的查询来取保web网页提交的字符串不会被解析为SQL命令。在本教程我们将整合用户输入为参数化的查询。

1.1.调用查询返回实体:

  DbSet<TEntity>类提供了一个执行查询返回一个TEntity实体类型的方法。

  修改DepartmentController.cs的Details方法:

public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
} // Commenting out original code to show how to use a raw SQL query.
//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);
}

1.2.调用查询返回其他类型的对象:

  之前我们在About页面创建了一个student统计表格,显示每个enrollment date的student数量。LINQ代码如下:

var data = from student in db.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};

  假设我们想使用SQL代替LINQ。并且需要返回非实体对象,我们就需要使用Database.SqlQuery方法。

  修改HomeController.csAbout方法:

public ActionResult About()
{
// Commenting out LINQ to show how to do the same thing in SQL.
//IQueryable<EnrollmentDateGroup> = from student in db.Students
// group student by student.EnrollmentDate into dateGroup
// select new EnrollmentDateGroup()
// {
// EnrollmentDate = dateGroup.Key,
// StudentCount = dateGroup.Count()
// }; // SQL version of the above LINQ code.
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());
}

1.3.调用Update查询:

  在CourseContoller.cs添加UpdateCourseCredits方法:

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();
}

  添加视图:

@model ContosoUniversity.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>

  运行:

  更多关于原生SQL查询信息请查看:Raw SQL Queries

2.无追踪(No-Tracking)查询

  当一个数据库上下文搜索到表中的行数据会为他们创建实体对象,默认情况下它会一直追踪内存中的实体是否和数据库中的数据保持一致。内存中的数据作为缓存和更新实体时使用。这种缓存在web应用程序中通常是没有必要的,因为上下文实例通常是短暂的(每次请求都会新建和销毁一个上下文实例),并且通常上下文读取的实例在该实例再次被使用前就已经销毁 。

  我们可以使用AsNoTracking方法禁用内存对象中实体对象的追踪。在下面几个典型的场景中我们可能想要这么做:

  • 一个查询检索大量的数据,关闭追踪会显著的提高性能。
  • 当我们为了更新而附加一个实体时,但是我们之前为了其他的目的已经获取了该实体。因为该实体已经能够被数据库上下文追踪,我们不能附加这个想要修改的实体。这种情况下的一种处理方式是对之前的查询使用AsNoTracking选项。

  AsNoTracking方法使用实例请参考:the earlier version of this tutorial。本教程在Edit方法的实体模型-绑定-创建时没有设置修改标志,所以没有必要使用AsNoTracking

3.检查发送到数据库的SQL

  修改Controllers/CourseController的Index方法,暂时禁用显式加载:

public ActionResult Index()
{
var courses = db.Courses;
var sql = courses.ToString();
return View(courses.ToList());
}

  在return语句设置断点,运行程序,查看sql变量的值:

  在Course的Index页面添加下拉列表,用于删选department数据。修改CourseController.csIndex方法:

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());
}

  重新在return语句设置断点。

  修改Views\Course\Index.cshtml,在table标签之前:

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

  运行:

  查看sql变量的值:

SELECT
[Project1].[CourseID] AS [CourseID],
[Project1].[Title] AS [Title],
[Project1].[Credits] AS [Credits],
[Project1].[DepartmentID] AS [DepartmentID],
[Project1].[DepartmentID1] AS [DepartmentID1],
[Project1].[Name] AS [Name],
[Project1].[Budget] AS [Budget],
[Project1].[StartDate] AS [StartDate],
[Project1].[InstructorID] AS [InstructorID],
[Project1].[RowVersion] AS [RowVersion]
FROM ( SELECT
[Extent1].[CourseID] AS [CourseID],
[Extent1].[Title] AS [Title],
[Extent1].[Credits] AS [Credits],
[Extent1].[DepartmentID] AS [DepartmentID],
[Extent2].[DepartmentID] AS [DepartmentID1],
[Extent2].[Name] AS [Name],
[Extent2].[Budget] AS [Budget],
[Extent2].[StartDate] AS [StartDate],
[Extent2].[InstructorID] AS [InstructorID],
[Extent2].[RowVersion] AS [RowVersion]
FROM [dbo].[Course] AS [Extent1]
INNER JOIN [dbo].[Department] AS [Extent2] ON [Extent1].[DepartmentID] = [Extent2].[DepartmentID]
WHERE @p__linq__0 IS NULL OR [Extent1].[DepartmentID] = @p__linq__1
) AS [Project1]
ORDER BY [Project1].[CourseID] ASC

4.仓储和工作单元模式

  许多开发者编写代码实现仓储和工作单元模式作为包装器代码配合EF使用。这些模式的目的是在数据访问层和业务逻辑层之间创建一个抽象层。实现这种模式可以将程序与数据存储变化隔离,并且有利于单元测试和测试驱动开发(TDD)。但是编写额外的代码实现这些模式,对使用EF的程序并不总是最好的选择,原因如下:

  • EF上下文类本身就实现了代码和数据存储的隔离。
  • 当使用EF更新数据库时,EF上下文类可以当作工作单元类。
  • EF6的特性使不编写仓储代码实现TDD变得更加容易。

  更多关于实现仓储和工作单元模式信息请参考:the Entity Framework 5 version of this tutorial series。更多关于在EF6中实现TDD信息请参考:

5.代理类

  当EF创建实体实例时(例如,执行查询时),通常把它们创建可以作为实体代理的动态的派生类型。例如,下面两张调试图片。从第一张可以看到student变量在实例化实体后直接是所期望的Student类型。在第二张图片中,当EF被用来从数据库读取一个student实体时使用的是代理类:

  代理类覆盖了实体的一些virtual属性,用来插入钩子在访问属性时自动执行操作。这种机制的一个功能是用于延迟加载。

  大部分情况下,我们不用关心这些代理的使用,除了一些几种情况:

  • 在一些场景中,我们或许想要阻止EF创建代理实例。例如,当我们序列化产生的实体,需要POCO类时,不要代理类。一种解决序列化问题的做法是序列化数据传输对象(DTO)而不是实体对象,请参考:Using Web API with Entity Framework。另一种方法是:disable proxy creation
  • 当我们使用new实例化一个实体类时,不会产生代理实例。这意味着我们不使用 延迟加载和自动修改追踪功能。这通常是没有问题的,我们通常不需要延迟加载,因为我们不是在数据库中创建一个实体,并且我们通常也不需要修改追踪,如果我们特别标记实体为Added。然而,我们需要延迟加载和修改追踪,我们可以使用DbSet的Create方法创建使用代理的实体实例。
  • 我们想要从代理类型获取实际的实体类型。我们可以使用ObjectContext类的GetObjectType方法从代理类型实例获取实际的实体类型。

  更多信息请参考:Working with Proxies

6.自动检测改变

  EF根据比较实体原始值和当前值实体来确定实体是否被改变(哪个update需要被发送的数据库)。原始值在实体被查询或附加时存储。下面的一些方法会自动检测改变:

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

  当我们追踪大量的实体,并且在循环中多次调用上面的方法时,使用AutoDetectChangesEnabled属性暂时关闭自动检测改变功能,来获取显著的性能提升。更多信息请查看:Automatically Detecting Changes

7.EF Power Tools

  Entity Framework Power Tools是Visual Studio内置的用来创建本教程显示的数据模型图。该工具同样具有其他功能,例如根据已经存在的数据库的表产生实体类,这样就可以使用Code First。安装此工具后,上下文菜单会出现一些其他的选项。例如,当我们在Solution Explorer的上下文类中右键,会出现产生图表选项。当我们使用Code First时,我们不能改变图表中的数据模型,但是我们可以移动它们的位置,让它们变得容易理解。

8.EF源码

  EF6的源码在http://entityframework.codeplex.com/。除了源码,我们也可以获取nightly builds, issue tracking, feature specs, design meeting notes等。我们可以修复bug,并且贡献我们自己的EF6增强代码。

  尽管EF是开源的,但是它是微软完全支持的产品。微软的EF团队控制接受哪些贡献的代码并对所有的更改进行测试以确保每个发行版本的质量。

9.总结

  更多关于如何使用EF处理数据的信息,请查看:EF documentation page on MSDNASP.NET Data Access - Recommended Resources

  更多关于如何部署编译后的web程序,请查看:ASP.NET Web Deployment - Recommended Resources

  更多关于MVC的其他话题,请查看:ASP.NET MVC - Recommended Resources

10.常见错误以及它们的解决方案或者替代方案

Cannot create/shadow copy

Error Message:

Cannot create/shadow copy '<filename>' when that file already exists.

Solution

等待几秒并刷新页面。

Update-Database not recognized

Error Message (from the Update-Database command in the PMC):

The term 'Update-Database' is not recognized as the
name of a cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is correct and try
again.

Solution

关闭Visual Studio,重启项目并重试。

Validation failed

Error Message (from the Update-Database command in the PMC):

Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.

Solution

这个问题的的其中一个原因是种子方法运行时验证错误.  查看Seeding and Debugging Entity Framework (EF) DBs关于调试Seed方法的建议。

HTTP 500.19 error

Error Message:

HTTP Error 500.19 - Internal Server Error
The requested page cannot be accessed because the related configuration data for
the page is invalid.

Solution

这个错误的一个原因是解决方案有多个副本,并且这些副本使用相同的端口号。通常我们可以通过关闭所有的Visual Studio,然后重启项目来解决此问题。如果上述方法不奏效,就改变端口号。右键项目,点击properties,选择Web标签在Project Url修改端口号。

Error locating SQL Server instance

Error Message:

A network-related or instance-specific error occurred
while establishing a connection to SQL Server. The server was not found or was
not accessible. Verify that the instance name is correct and that SQL Server is
configured to allow remote connections. (provider: SQL Network Interfaces,
error: 26 - Error Locating Server/Instance Specified)

Solution

查看连接字符串。如果手动删除了数据库,则修改连接字符串的数据库名。

[翻译][MVC 5 + EF 6] 12[完结]:高级场景的更多相关文章

  1. [翻译][MVC 5 + EF 6] 5:Code First数据库迁移与程序部署

    原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用 ...

  2. [翻译][MVC 5 + EF 6] 11:实现继承

    原文:Implementing Inheritance with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.选择继承映射到数据库 ...

  3. [翻译][MVC 5 + EF 6] 10:处理并发

    原文:Handling Concurrency with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.并发冲突: 当一个用户编辑一 ...

  4. [翻译][MVC 5 + EF 6] 9:异步和存储过程

    原文:Async and Stored Procedures with the Entity Framework in an ASP.NET MVC Application 1.为什么使用异步代码: ...

  5. [翻译][MVC 5 + EF 6] 8:更新相关数据

    原文:Updating Related Data with the Entity Framework in an ASP.NET MVC Application 1.定制Course的Create和E ...

  6. [翻译][MVC 5 + EF 6] 7:加载相关数据

    原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 1.延迟(Lazy)加载.预先(Eage ...

  7. [翻译][MVC 5 + EF 6] 6:创建更复杂的数据模型

    原文:Creating a More Complex Data Model for an ASP.NET MVC Application 前面的教程中,我们使用的是由三个实体组成的简单的数据模型.在本 ...

  8. [翻译][MVC 5 + EF 6] 4:弹性连接和命令拦截

    原文:Connection Resiliency and Command Interception with the Entity Framework in an ASP.NET MVC Applic ...

  9. [翻译][MVC 5 + EF 6] 3:排序、过滤、分页

    原文:Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application 1.添加排序: 1. ...

随机推荐

  1. U盘安装SLES的方法

    安装准备: 一个4G 或 大于4G的U盘 也同样适合移动硬盘, SLES-11-DVD-i586-GM-DVD1.iso 文件1) 将U盘格式化成FAT32格式; 2) 下载 syslinux工具 h ...

  2. IOS 在Ipad 横屏 上使用UIImagePickerController

    转载前请注明来源:http://www.cnblogs.com/niit-soft-518/p/4381328.html 最近在写一个ipad的项目,该项目必须是横屏.进入正题,有一项功能是要调用系统 ...

  3. 辛星浅谈PHP的混乱的编码风格

    我们都知道.各种编程语言都有自己的风格,即使是像C和C++那样一脉相承的语言(C++本意全然兼容C的语法).编程风格上还是有些区别.比方非常典型的就是C++风格的单行凝视和C风格的多行凝视. 而尽管J ...

  4. JS保留两位小数 四舍五入函数

    js 四舍五入函数 toFixed(),里面的参数 就是保留小数的位数. <script language="javascript"> document.write(& ...

  5. hdu 5445 Food Problem 多重背包

    Food Problem Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=5 ...

  6. MST最小生成树及克鲁斯卡尔(Kruskal)算法

    最小生成树MST,英文名如何拼写已忘,应该是min spaning tree吧.假设一个无向连通图有n个节点,那么它的生成树就是包括这n个节点的无环连通图,无环即形成树.最小生成树是对边上权重的考虑, ...

  7. xss脚本攻击

    xss脚本攻击不仅仅只是alert(1)就算完了,xss脚本攻击真正的用处是盗取普通用户的cookie,或者盗取管理员的cookie. xss分类(类型): 1. 反射型xss2. 存储型xss3. ...

  8. JAVA构造器、this、super

    构造器是为了创建一个类的实例.这个过程也可以在创建一个对象的时候用到: Platypus p1 = new Platypus(); 相反,方法的作用是为了执行java代码. 修饰符,返回值和命名的不同 ...

  9. foo,bar,baz

    https://en.wikipedia.org/wiki/Foobar 原文: The terms foobar, foo, bar, baz and qux are sometimes used ...

  10. iis7下.Net框架版本设置

    转载:http://blog.163.com/fan_yishan/blog/static/47692213201391651229542/ Win7下IIS网站的.Net框架版本设置 步骤/方法 1 ...