原文:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application
1.执行原生SQL查询:
EF Code First API包含直接传递SQL命令到数据库的方法:
使用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.cs的About
方法:
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.cs的Index
方法:
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的程序并不总是最好的选择,原因如下:
更多关于实现仓储和工作单元模式信息请参考:the Entity Framework 5 version of this tutorial series。更多关于在EF6中实现TDD信息请参考:
5.代理类:
当EF创建实体实例时(例如,执行查询时),通常把它们创建可以作为实体代理的动态的派生类型。例如,下面两张调试图片。从第一张可以看到student变量在实例化实体后直接是所期望的Student类型。在第二张图片中,当EF被用来从数据库读取一个student实体时使用的是代理类:
代理类覆盖了实体的一些virtual属性,用来插入钩子在访问属性时自动执行操作。这种机制的一个功能是用于延迟加载。
大部分情况下,我们不用关心这些代理的使用,除了一些几种情况:
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 MSDN和 ASP.NET Data Access - Recommended Resources。
更多关于如何部署编译后的web程序,请查看:ASP.NET Web Deployment - Recommended Resources。
更多关于MVC的其他话题,请查看:ASP.NET MVC - Recommended Resources。
10.常见错误以及它们的解决方案或者替代方案:
Error Message:
Cannot create/shadow copy '<filename>' when that file already exists.
Solution
等待几秒并刷新页面。
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,重启项目并重试。
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方法的建议。
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 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
查看连接字符串。如果手动删除了数据库,则修改连接字符串的数据库名。