共用方式為


E教學課程:在 ASP.NET MVC 應用程式中將非同步過程和預存程序與 EF 結合使用

在前面的教學課程中,您學習如何使用同步程式設計模型讀取和更新資料。 在本教學課程中,您將了解如何實作非同步程式設計模型。 非同步程式碼可以幫助應用程式更好地執行,因為它可以更好地利用伺服器資源。

在本教學課程中,您還將了解如何使用預存程序對實體進行插入、更新和刪除操作。

最後,將應用程式以及自首次部署以來實施的所有資料庫變更重新部署到 Azure。

下列圖例顯示了您將操作的一些頁面。

部門頁面

建立部門

在本教學課程中,您已:

  • 了解非同步程式碼
  • 建立部門控制器
  • 使用預存程序
  • 部署至 Azure

必要條件

為什麼要使用非同步程式碼

網頁伺服器的可用執行緒數量有限,而且在高負載情況下,可能會使用所有可用的執行緒。 發生此情況時,伺服器將無法處理新的要求,直到執行緒空出來。 使用同步程式碼,許多執行緒可能在實際上並未執行任何工作時受到占用,原因是在等候 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());
}

應用了四項變更來啟用實體框架資料庫查詢非同步執行:

  • asyncTask<ActionResult>此方法以關鍵字標記,它告訴編譯器為方法體的部分產生回呼並自動建立傳回的物件。
  • ActionResultTask<ActionResult>返回類型從 更改為 。 Task<T>T此類型表示正在進行的工作,其結果為 type 。
  • await此關鍵字已套用於 Web 服務呼叫。 當編譯器看到這個關鍵字時,它會在幕後將該方法分成兩個部分。 第一部分以非同步啟動的操作結束。 第二部分放入操作完成時所呼叫的回呼方法。
  • 呼叫了 ToList 擴充方法的非同步版本。

為什麼修改的是 departments.ToList 聲明而不是 departments = db.Departments 聲明呢? 原因是只有導致查詢或指令傳送到資料庫的語句才會非同步執行。 departments = db.Departments 語句設定了一個查詢,但在呼叫 ToList 方法之前該查詢不會執行。 因此,只有 ToList 方法是非同步執行的。

Details 方法和 HttpGet 方法中,Edit 方法是導致查詢發送到資料庫的 Delete 方法,因此這是非同步執行的 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);
}

CreateHttpPost EditDeleteConfirmedSaveChanges 方法中,是方法呼叫導致執行指令,而不是諸如僅導致記憶體中的實體被修改的語句 db.Departments.Add(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>

此程式碼將標題從“索引”變更為“部門”,將管理員名稱移至右側,並提供管理員的全名。

在建立、刪除、詳細資訊和編輯檢視中,將 InstructorID 欄位標題變更為「管理員」,就像在課程檢視中將部門名稱欄位變更為「部門」樣。

在建立和編輯檢視中使用以下程式碼:

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

在刪除和詳細資料檢視中使用以下程式碼:

<dt>
    Administrator
</dt>

運行該應用程序,然後按一下 Departments 索引標籤。

一切工作方式與其他控制器相同,但在此控制器中所有 SQL 查詢都是非同步執行的。

在實體框架中使用非同步程式設計時需要注意的一些事項:

  • 非同步程式碼不是執行緒安全的。 換句話說,不要嘗試使用相同上下文執行個體並行執行多個操作。
  • 若您想要充分利用非同步程式碼帶來的效能優點,請確保任何您正在使用的程式庫 (例如分頁) 也使用了非同步 (若它們有呼叫任何可能會傳送查詢到資料庫的 Entity Framework 方法的話)。

使用預存程序

一些開發人員和 DBA 更喜歡使用預存程序進行資料庫存取。 在實體框架的早期版本中,您可以透過執行原始 SQL 查詢來使用預存程序檢索資料,但無法指示 EF 使用預存程序進行更新操作。 在 EF 6 中,可以輕鬆設定 Code First 以使用預存程序。

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

    此程式碼指示 Department 實體框架使用預存程序對實體進行插入、更新和刪除操作。

  2. 在套件管理控制台中,輸入以下命令:

    add-migration DepartmentSP

    開啟 Migrations\timestamp_DepartmentSP.cs<,查看建立>Insert、Update 和 DeleteUp 預存程序的方法中的程式碼:

    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. 在套件管理控制台中,輸入以下命令:

    update-database

  4. 在偵錯模式下執行應用程序,按一下部門索引標籤,然後按一下新建

  5. 輸入新部門的資料,然後按一下建立

  6. 在 Visual Studio 中,檢視輸出視窗中的日誌,可以看到預存程序用於插入新的「部門」行。

    部門插入 SP

Code First 建立預設的預存程序名稱。 如果您使用現有資料庫,則可能需要自訂預存程序名稱才能使用資料庫中已定義的預存程序。 有關如何執行此操作的信息,請參閱實體框架程式碼優先插入/更新/刪除預存程序

如果要自訂產生的預存程序的功能,可以編輯建立預存程序的遷移 Up 方法的支架代碼。 這樣,只要執行遷移,您的變更就會反映,並且當遷移在部署後在生產中自動運行時,您的變更將應用於您的生產資料庫。

如果要變更先前遷移中建立的現有預存程序,可以使用 Add-Migration 指令產生空白遷移,然後手動編寫呼叫 AlterStoredProcedure 方法的程式碼。

部署至 Azure

本部分要求您完成本系列的遷移和部署教學課程中的可選的將應用程式部署到 Azure 部分。 如果您遇到了透過刪除本機專案中的資料庫解決的遷移錯誤,請跳過本部分。

  1. 在 Visual Studio 中,以滑鼠右鍵按一下解決方案資源管理器中的項目,然後從上下文功能表中選擇發佈

  2. 按一下 [發行]

    Visual Studio 將應用程式部署到 Azure,然後應用程式在預設瀏覽器中開啟並在 Azure 中執行。

  3. 測試應用程式以驗證其是否正常運作。

    第一次執行存取資料庫的頁面時,實體框架會執行使資料庫與目前資料模型保持同步所需的所有遷移 Up 方法。 現在您可以使用自上次部署以來新增的所有網頁,包括您在本教學課程中新增的部門頁面。

取得程式碼

下載已完成的項目

其他資源

可以在 ASP.NET 資料存取 - 推薦資源中找到其他實體框架資源的連結。

下一步

在本教學課程中,您已:

  • 了解非同步程式碼
  • 建立了部門控制器
  • 使用的儲存過程
  • 部署到 Azure

繼續閱讀下一篇文章,了解當多個使用者同時更新同一實體時如何處理衝突。