教程:在 ASP.NET MVC 应用中将异步和存储过程与 EF 配合使用
在前面的教程中,你学习了如何使用同步编程模型读取和更新数据。 本教程介绍如何实现异步编程模型。 异步代码可以帮助应用程序更好地执行,因为它可以更好地利用服务器资源。
本教程还介绍了如何使用存储过程对实体执行插入、更新和删除操作。
最后,将应用程序重新部署到 Azure,以及自首次部署以来实现的所有数据库更改。
下图是一些将会用到的页面。
在本教程中,你将了解:
- 了解异步代码
- 创建部门控制器
- 使用存储过程
- 部署到 Azure
先决条件
为何使用异步代码
Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。 使用同步代码时,可能会出现多个线程被占用但不能执行任何操作的情况,因为它们正在等待 I/O 完成。 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。 因此,异步代码使服务器资源能够更高效地使用,并且服务器能够处理更多的流量,而不会延迟。
在早期版本的 .NET 中,编写和测试异步代码非常复杂、容易出错且难以调试。 在 .NET 4.5 中,编写、测试和调试异步代码要容易得多,除非有理由不这样做,否则通常应该编写异步代码。 异步代码确实引入了少量的开销,但对于低流量情况,性能命中率微乎其微,而对于高流量情况,潜在的性能改进是实质性的。
有关异步编程的详细信息,请参阅 使用 .NET 4.5 的异步支持以避免阻塞调用。
创建部门控制器
创建与之前控制器相同的部门控制器,但这次选中“ 使用异步控制器操作 ”复选框。
以下突出显示显示了向同步代码添加的内容,使 Index
该方法成为异步代码:
public async Task<ActionResult> Index()
{
var departments = db.Departments.Include(d => d.Administrator);
return View(await departments.ToListAsync());
}
应用了四项更改,使 Entity Framework 数据库查询能够异步执行:
- 该方法使用
async
关键字进行标记,该关键字指示编译器为方法正文的各个部分生成回调,并自动创建Task<ActionResult>
返回的对象。 - 返回类型已从
ActionResult
Task<ActionResult>
更改为 。 此Task<T>
类型表示使用类型T
的结果正在进行的工作。 - 关键字
await
已应用于 Web 服务调用。 当编译器看到此关键字时,它会在后台将该方法拆分为两个部分。 第一部分以异步启动的操作结束。 第二部分将放入回调方法中,该方法在操作完成时调用。 - 调用扩展方法的
ToList
异步版本。
为什么语句 departments.ToList
已修改,但未 departments = db.Departments
修改该语句? 原因是只异步执行导致查询或命令发送到数据库的语句。 该 departments = db.Departments
语句设置查询,但在调用该方法之前 ToList
不会执行查询。 因此,仅 ToList
异步执行该方法。
在Details
方法和Delete
HttpGet
Edit
方法中,Find
该方法是导致查询发送到数据库的方法,因此是异步执行的方法:
public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Department department = await db.Departments.FindAsync(id);
if (department == null)
{
return HttpNotFound();
}
return View(department);
}
在 Create
和DeleteConfirmed
方法中,它是SaveChanges
导致命令执行的方法调用,而不是导致内存中的实体被修改的db.Departments.Add(department)
语句。 HttpPost Edit
public async Task<ActionResult> Create(Department department)
{
if (ModelState.IsValid)
{
db.Departments.Add(department);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
打开 Views\Department\Index.cshtml,并将模板代码替换为以下代码:
@model IEnumerable<ContosoUniversity.Models.Department>
@{
ViewBag.Title = "Departments";
}
<h2>Departments</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
Administrator
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
@Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
</td>
</tr>
}
</table>
此代码将标题从索引更改为部门,将管理员名称移到右侧,并提供管理员的完整名称。
在“创建”、“删除”、“详细信息”和“编辑”视图中,将 InstructorID
字段的标题更改为“管理员”,就像在课程视图中将部门名称字段更改为“Department”。
在“创建”和“编辑”视图中,使用以下代码:
<label class="control-label col-md-2" for="InstructorID">Administrator</label>
在“删除”和“详细信息”视图中,使用以下代码:
<dt>
Administrator
</dt>
运行应用程序,然后单击“ 部门 ”选项卡。
所有操作都与其他控制器相同,但在此控制器中,所有 SQL 查询都以异步方式执行。
在实体框架中使用异步编程时需要注意的一些事项:
- 异步代码不是线程安全的。 换句话说,不要尝试使用相同的上下文实例并行执行多个操作。
- 如果你想要利用异步代码的性能优势,请确保你所使用的任何库和包在它们调用导致 Entity Framework 数据库查询方法时也使用异步。
使用存储过程
某些开发人员和 DBA 更喜欢使用存储过程进行数据库访问。 在早期版本的 Entity Framework 中,可以通过执行原始 SQL 查询来使用存储过程检索数据,但不能指示 EF 使用存储过程执行更新操作。 在 EF 6 中,可以轻松地将 Code First 配置为使用存储过程。
在 DAL\SchoolContext.cs中,将突出显示的代码添加到
OnModelCreating
方法。protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("InstructorID") .ToTable("CourseInstructor")); modelBuilder.Entity<Department>().MapToStoredProcedures(); }
此代码指示 Entity Framework 使用存储过程对实体执行插入、更新和删除操作
Department
。在包管理控制台中,输入以下命令:
add-migration DepartmentSP
打开 Migrations\<timestamp>_DepartmentSP.cs 在创建插入、更新和删除存储过程的方法中
Up
查看代码:public override void Up() { CreateStoredProcedure( "dbo.Department_Insert", p => new { Name = p.String(maxLength: 50), Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"), StartDate = p.DateTime(), InstructorID = p.Int(), }, body: @"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID]) VALUES (@Name, @Budget, @StartDate, @InstructorID) DECLARE @DepartmentID int SELECT @DepartmentID = [DepartmentID] FROM [dbo].[Department] WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity() SELECT t0.[DepartmentID] FROM [dbo].[Department] AS t0 WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID" ); CreateStoredProcedure( "dbo.Department_Update", p => new { DepartmentID = p.Int(), Name = p.String(maxLength: 50), Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"), StartDate = p.DateTime(), InstructorID = p.Int(), }, body: @"UPDATE [dbo].[Department] SET [Name] = @Name, [Budget] = @Budget, [StartDate] = @StartDate, [InstructorID] = @InstructorID WHERE ([DepartmentID] = @DepartmentID)" ); CreateStoredProcedure( "dbo.Department_Delete", p => new { DepartmentID = p.Int(), }, body: @"DELETE [dbo].[Department] WHERE ([DepartmentID] = @DepartmentID)" ); }
在包管理控制台中,输入以下命令:
update-database
在调试模式下运行应用程序,单击“ 部门 ”选项卡,然后单击“ 新建”。
输入新部门的数据,然后单击“ 创建”。
在 Visual Studio 中 ,查看“输出 ”窗口中的日志,以查看存储过程用于插入新的 Department 行。
Code First 创建默认存储过程名称。 如果使用现有数据库,可能需要自定义存储过程名称,才能使用数据库中已定义的存储过程。 有关如何执行此操作的信息,请参阅 Entity Framework Code First Insert/Update/Delete 存储过程。
如果要自定义生成的存储过程执行的操作,可以编辑创建存储过程的迁移 Up
方法的基架代码。 这样,每当迁移运行时,更改都会反映,在部署后迁移在生产环境中自动运行时,将应用于生产数据库。
如果要更改在以前的迁移中创建的现有存储过程,可以使用“添加迁移”命令生成空白迁移,然后手动编写调用 AlterStoredProcedure 方法的代码。
部署到 Azure
本部分要求完成本系列迁移和部署教程中的可选“将应用部署到 Azure”部分。 如果在本地项目中删除数据库时发生了迁移错误,请跳过本部分。
在 Visual Studio 中,在“解决方案资源管理器”中右键单击项目,并从上下文菜单中选择“发布”。
单击“发布”。
Visual Studio 将应用程序部署到 Azure,并在 Azure 中运行的默认浏览器中打开该应用程序。
测试应用程序以验证它是否正常工作。
首次运行访问数据库的页时,Entity Framework 将运行使数据库与当前数据模型保持最新所需的所有迁移
Up
方法。 现在,可以使用自上次部署以来添加的所有网页,包括本教程中添加的“部门”页。
获取代码
其他资源
可以在 ASP.NET 数据访问 - 建议的资源中找到 指向其他 Entity Framework 资源的链接。
后续步骤
在本教程中,你将了解:
- 了解异步代码
- 创建了部门控制器
- 已用存储过程
- 部署到 Azure
请继续学习下一篇文章,了解如何在多个用户同时更新同一实体时处理冲突。