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

原文:Async and Stored Procedures with the Entity Framework in an ASP.NET MVC Application

1.为什么使用异步代码

  一个服务器可用的线程数量是有限的,在高负载的情况下所有的可用线程都可能在使用。在这种情况下,在线程被释放之前服务器不能处理新的请求。在同步代码中,很多线程被占用但是实际上没有做任何操作,因为它们在等待I/O完成。在异步代码中,当一个进程在等待I/O完成的过程中,它的线程将会被释放用于处理其他请求。这样,异步代码可以更高效地使用服务器资源,并且使服务器可以没有延迟地处理更多访问。

  在之前的.NET版本中,编写和测试异步代码比较复杂、容易出错并且难以调试。在.NET 4.5中,编写、测试和调试异步代码变得非常简单,通常我们需要编写异步代码,除非我们有一个不这样做的理由。异步代码引入少量的开销,对于低流量情况下性能下降可以忽略不计,而对于高流量情况下性能改善的潜力巨大。

  更多关于异步编程信息请查看:Use .NET 4.5’s async support to avoid blocking calls

2.新建Department控制器

  查看产生的Index方法的代码:

public async Task<ActionResult> Index()
{
    var departments = db.Departments.Include(d => d.Administrator);
    return View(await departments.ToListAsync());
}
  • 方法使用async 关键字标记,用来告诉编译器为方法体的部分产生回调,并且自动创建返回的Task<ActionResult>对象。
  • 返回类型从ActionResult变为 Task<ActionResult>
  • await关键字被用于web service调用。当编译器遇到这个关键字,会在幕后将该方法分为两部分。第一部分以异步操作开始时结束。第二部分被放入操作完成后调用的回调方法中。
  • 异步版本的ToList扩展被调用。

  为什么 departments.ToList语句被修改而departments = db.Departments语句没有被修改呢?因为,只有执行查询或者被发送到数据库的命令才会异步执行。departments = db.Departments语句生成查询,但是查询在 departments.ToList语句才被执行。因此,只有ToList方法被异步执行。下面的代码中亦是同一原因:

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

  在Create、Delete、Details和Edit视图,将 InstructorID的标题改为“Administrator”:

    在Create和Edit视图:

<label class="control-label col-md-2" for="InstructorID">Administrator</label>

    在Delete和Details视图:

<dt>
    Administrator
</dt>

  运行:

  在使用EF异步编程需要注意的问题:

  • 异步代码是线程不安全的。因此,不要试图使用同一个上下文实例并行处理多个操作。
  • 如果想要利用异步代码的性能优势,需要保证我们使用的库包(例如分页)在调用任何EF方法查询数据库时同样使用异步。

3.使用存储过程插入、更新和删除

  在早期的EF版本中,我们可以通过执行原生的SQL查询来使用存储过程查询数据,但是不能使用存储过程执行更新操作。在EF6中很容易配置Code First使用存储过程。

3.1.在DAL\SchoolContext.cs中添加代码:

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

  该行代码告诉EF在对Department进行插入、更新和删除操作时使用存储过程。

3.2.在Package Manage Console输入命令:

add-migration DepartmentSP

  打开Migrations\<timestamp>_DepartmentSP.cs,可以看到:

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)"
    );

}

3.3.在Package Manage Console输入命令:

update-database

3.4.运行:

  Code First创建默认的存储过程名。如果我们使用一个已经存在的数据库,为了使用数据库中已经定义的存储过程,我们需要指定存储过程的名字。更多信息请参考: Entity Framework Code First Insert/Update/Delete Stored Procedures

  如果我们想要指定产生的存储过程做什么,我们可以编辑迁移中的Up方法中产生存储过程的代码。通过这种方式,我们修改的代码不管是迁移运行时还是当部署后迁移自动在产品数据库运行都会起作用。

  如果我们想要修改一个在早期的迁移中创建的存储过程,我们可以使用Add-Migration命令来产生一个空白的迁移,然后手动编写调用AlterStoredProcedure方法的代码。

4.部署到Azure

  本节操作需要完成了之前可选的部署到Azure。如果我们遇到迁移错误,并且决定通过删除本地项目中的数据库来解决此问题,那么可以跳过本节。

4.1.在Visual Studio中选中项目右键,Solution Explorer选中Publish

4.2.点击Publish

4.3.测试程序是否正常工作。

其他的EF资源:ASP.NET Data Access - Recommended Resources

时间: 2024-08-29 23:40:17

[翻译][MVC 5 + EF 6] 9:异步和存储过程的相关文章

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

原文:Handling Concurrency with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.并发冲突: 当一个用户编辑一个实体数据时,另一个用户在第一个用户的改变提交到数据库之前同时也在编辑这个实体数据,这时就会发生冲突.如果不处理这种冲突,最后更新数据库的用户的更改将覆盖其他用户的修改.在许多程序中,这种风险是可以接受的:如果程序具有较少的用户和较少的更新操作,或者不是覆盖关键的变化,这种情况下处理并发的成本可能大

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

原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 1.延迟(Lazy)加载.预先(Eager)加载.显式(Explicit)加载: EF加载相关数据到实体导航属性有以下几种方式: 延迟加载:当实体第一次读取时,相关数据没有加载.当第一次试图访问导航属性时,所需的导航数据自动加载.这导致多条查询语句被发送到数据库:一条查询实体本身,一条查询实体相关数据.DbContext类默认启用延迟加载

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

原文:Creating a More Complex Data Model for an ASP.NET MVC Application 前面的教程中,我们使用的是由三个实体组成的简单的数据模型.在本教程中,我们将添加更多的实体和关系,并通过指定格式.验证和数据库映射规则来自定义数据模型.有两种方式来定义数据模型:一种是给实体类添加属性,另一种是在数据库上下文类添加代码. 当我们完成后,实体类将完成下图所示的数据模型: 1.通过使用属性来自定义数据模型: 1.1.DateType属性: 修改Mo

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

原文:Implementing Inheritance with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.选择继承映射到数据库表: 在School数据模型里面,Instructor和Student类有几个属性是相同的: 假设我们想要消除Instructor和Student实体属性的冗余代码.或者我们想要编写一个可以格式化name的服务,而不用考虑这个name是来自一个instructor还是student.我们可以创建一个

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

原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用Code First迁移: 当我们开发一个新的程序时,数据模型经常会发生改变,每次模型发生改变时,就会变得与数据库不同步.我们之前配置EF在每次数据模型发生改变时自动删除然后重建数据库.当我们增加.删除或者改变实体类或者改变DbContext类时,在程序下次运行时将会自动删除已经存在的数据库,并且创

[翻译][MVC 5 + EF 6] 1:准备工作

原文:Getting Started with Entity Framework 6 Code First using MVC 5 1.新建MVC项目: 2.修改Views\Shared\_Layout.cshtml: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=d

[翻译][MVC 5 + EF 6] 4:弹性连接和命令拦截(Command Interception)

原文:Connection Resiliency and Command Interception with the Entity Framework in an ASP.NET MVC Application [注:本节教程可以选择性学习] 本节教程将学习EF6的两个重要特性,这两个特性在我们将程序部署在云环境时特别有用: 弹性连接(connection resiliency):遇到瞬时的连接错误时自动重试连接. 命令拦截(command interception):捕获所有发送到数据库的查询

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

原文:Updating Related Data with the Entity Framework in an ASP.NET MVC Application 1.定制Course的Create和Edit页面: 修改CourseController.cs的Create和Edit方法: public ActionResult Create() { PopulateDepartmentsDropDownList(); return View(); } [HttpPost] [ValidateAnt

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

原文:Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application 1.添加排序: 1.1.修改Controllers\StudentController.cs的Index: public ActionResult Index(string sortOrder) { ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "na