為ASP.NET MVC應用程式使用非同步及預存程序
這是微軟官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這裡是第九篇:為ASP.NET MVC應用程式使用非同步及預存程序
原文: Async and Stored Procedures with the Entity Framework in an ASP.NET MVC Application
在之前的教程中,您已經學習了如何使用同步編程模型來讀取和更新資料。在本教程中您將看到如何?非同步編程模型。因為能夠更好地使用伺服器資源,非同步代碼可以輔助應用程式更好地執行。
在本教程中您還會看到如何使用預存程序對實體進行插入、更新和刪除操作。
下面的插圖顯示了您將要編寫的頁面:
為什麼要使用麻煩的非同步代碼
一個WEB伺服器只有很有限的可用線程,並且在高負載的情況下,可能所有的線程都會在使用中。當發生這種情況時,伺服器將無法處理新的請求,直到有線程被釋放。在同步代碼的情況下,多個線程可能會關聯起來,但實際上它們並不作任何工作而只是在等待IO完成。使用非同步代碼,當一個進程正在等待IO完成時,它的線程可以伺服器騰出從而用於處理其他請求。因此,非同步代碼可以更高效地使用伺服器資源,並且伺服器能夠在不延遲的情況下處理更多的流量。
在較早版本的.NET中,編寫和測試非同步代碼是一件複雜、容易出錯且難以調試的工作。在.Net 4.5中,編寫、測試和調試非同步代碼變得簡單起來,你應當總是使用非同步代碼,除非有理由不允許你這樣做。非同步代碼會花費很少量的開銷,但對於低流量情況下效能的損失是微不足道的。對於高流量的情況下,潛在的效能提示是巨大的。
有關非同步編程的更多資訊,請參閱 Use .NET 4.5’s async support to avoid blocking calls。 建立系控制器
使用之前你建立其他控制器相同的方式來建立一個系控制器,但這次我們選擇使用非同步控制器操作選項。
下面的代碼中,高亮部分顯示了非同步方法呼叫和同步方法的不同之處:
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方法和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);}
在Create,HttpPost的Edit和DeleteConfirmed方法中,是SaveChanges方法導致命令執行,而像db.Department.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>
運行該應用程式,並點擊系選項卡。
程式正常地運行,就跟其他的控制器一樣。但在此控制器中,所有SQL查詢都是非同步執行的。
當您在Entity Framework中使用非同步編程要注意的一些事情: 非同步代碼不是安全執行緒的。換言之,不要使用同一個上下文執行個體以並行方式來執行多個操作。 如果你想要利用非同步代碼的效能優勢,請確保您正在使用的所有庫軟體包(例如分頁),在包中進行的資料庫查詢等任何Entity Framework方法也使用非同步執行。 用於插入、更新和刪除的預存程序
一些開發人員和DBA更願意使用預存程序來訪問資料庫。早期版本的Entity Framework中,您可以使用執行原始SQL查詢的方式來檢索資料來執行預存程序,但您不能使用預存程序來用於更新操作。在Entity Framework6中,您可以很容易地配置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\<時間戳記>_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
在調試模型下運行該應用程式,單擊系選項卡,然後單擊建立。 為一個新系輸入相關資料然後單擊建立。
在VS中的輸出視窗查看日誌。
Code First使用預設名建立了預存程序。如果您正在使用現有的資料庫,您可能需要自訂預存程序的名稱,有關如何操作的資訊,請參閱 Entity Framework Code First Insert/Update/Delete Stored Procedures 。
如果你想要自訂預存程序,你可以編輯遷移中的腳手架代碼裡的Up方法來建立預存程序。使用這種方法時您的更改將在應用遷移時或部署到生產環境後自動進行。
如果你想修改一個在之前的遷移中已經建立的的預存程序,你可以使用Add-Migration命令來產生一個空白的遷移,然後手動編寫代碼調用AlterStoredProcedure方法。 部署到Windows Azure
略… 總結
在本教程中,您看到了如何提高伺服器效率。通過編寫非同步執行代碼以及使用預存程序來進行插入、更新和刪除操作。在接下來的教程中,您將看到當多個使用者試圖編輯同一條紀錄時如何防止資料丟失。 作者資訊
Tom Dykstra- Tom Dykstra 是微軟Web平台及工具團隊的進階程式員,作家。