一、引言
在前面的幾篇文章中,我們主要講解了TPL,主要是Task,然后還介紹了如何在ASP.NET MVC中使用異步方法,在這篇文章中,我們結合一個小的實例,使用ASP.NET MVC+EF,實現異步的增刪改查。
二、功能實現
我們以學生為例,實現增刪改查功能。
1、搭建UI層
我們這里使用ASP.NET MVC作為界面層顯示數據,首先創建一個解決方案,然后添加一個MVC項目,命名為TaskAsync.UI,創建后的項目結構如下圖所示:
2、添加實體類
我們把實體類放在單獨的類庫里面,新建一個類型項目,命名為TaskAsync.Model,里面有一個Student類,Student類代碼如下:
namespace TaskAsync.Model { /// <summary> /// 學生類 /// </summary> public class Student { /// <summary> /// 主鍵 /// </summary> public int Id { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 年齡 /// </summary> public int Age { get; set; } /// <summary> /// 性別 /// </summary> public int Gender { get; set; } } }
3、添加服務接口層
我們把增刪改查的方法定義在接口里面,新添加一個類型項目,命名為TaskAsync.IService,需要引用上面創建的實體類庫。里面有一個IStudentService接口,接口代碼如下:
using System.Collections.Generic; using System.Threading.Tasks; using TaskAsync.Model; namespace TaskAsync.IService { public interface IStudentService { /// <summary> /// 增加的異步方法 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task<int> AddPersonAsync(Student entity); /// <summary> /// 刪除的異步方法 /// </summary> /// <param name="id"></param> /// <returns></returns> Task<int> DeleteByIdAsync(int id); /// <summary> /// 獲取所有數據 /// </summary> /// <returns></returns> Task<IEnumerable<Student>> GetAllAsync(); /// <summary> /// 根據Id獲取單一值 /// </summary> /// <param name="id"></param> /// <returns></returns> Task<Student> GetStudentByIdAsync(int id); /// <summary> /// 更新的異步方法 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task<int> UpdateAsync(Student entity); } }
所有的方法返回值都是Task<T>類型的,方法名稱默認以Async結尾,標注為異步方法。
4、添加Entity Framework
我們使用EF作為ORM框架,把EF放在單獨類庫里面,命名為TaskAsync.Data。直接在NuGet里面安裝:
安裝完成以后,我們同樣需要在創建的ASP.NET MVC程序里面EntityFramework,然后在外層的Web.config文件里面添加鏈接字符串:
<connectionStrings> <add name="EFContext" connectionString="Data Source=.;Initial Catalog=TaskAsyncDb;User ID=sa;Password=123456;" providerName="System.Data.SqlClient" /> </connectionStrings>
注意:鏈接字符串里面的providerName不能省略,否則進行數據遷移的時候會報錯。
我們在TaskAsync.Data項目里面添加數據上下文類,繼承自父類的DbContext:
using System.Data.Entity; using TaskAsync.Model; namespace TaskAsync.Data { /// <summary> /// 數據上下文類,繼承自父類的DbContext /// </summary> public class AppDbContext:DbContext { /// <summary> /// 通過創建連接,給父類的構造函數傳遞參數 /// 參數是連接字符串的名稱 /// 表示使用連接字符串中名字為DbConnectionString的去連接數據庫 /// </summary> public AppDbContext():base("name=DbConnectionString") { } /// <summary> /// 重寫OnModelCreating方法 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 配置生成的表名 modelBuilder.Entity<Student>().ToTable("T_Student"); base.OnModelCreating(modelBuilder); } public DbSet<Student> Students { get; set; } } }
數據上下文類創建完成以后,我們接下來在程序包管理器控制台里面進行數據遷移:
注意:項目要選擇EntityFramework所在的類庫項目。
1、開啟遷移
使用下面的命令開啟數據遷移:
Enable-Migrations
命令執行如下圖所示:
2、增加遷移
使用下面的命令開始遷移:
Add-Migration Init
命令執行如下圖所示:
執行成功以后,會在TaskAsync.Data項目下面添加一個Migrations文件夾
這個文件夾下面有兩個類文件:Configuration.cs文件里面是配置信息,另外一個是本次遷移記錄文件。我們在Configuration.cs類里面添加一些種子數據:
namespace TaskAsync.Data.Migrations { using System.Collections.Generic; using System.Data.Entity.Migrations; using System.Linq; using TaskAsync.Model; internal sealed class Configuration : DbMigrationsConfiguration<TaskAsync.Data.AppDbContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(TaskAsync.Data.AppDbContext context) { List<Student> list = new List<Student>() { new Student() { Name="Jack", Age=23, Gender=1 }, new Student() { Name="Tom", Age=25, Gender=2 } }; if(!context.Students.Any()) { context.Students.AddRange(list); } } } }
3、生成數據庫
我們在上面配置完成以后,就可以使用下面的命令去生成數據庫:
Update-Database
命令執行如下圖所示:
命令執行成功,就會自動創建數據庫和表,表里面插入我們添加的種子數據:
5、添加接口的實現類
我們添加IStudentService接口的實現類。添加一個單獨的類庫,命名為TaskAsync.Service,並添加對TaskAsync.Model、TaskAsync.IService、TaskAsync.Data的引用,然后實現IStudentService接口:
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TaskAsync.Data; using TaskAsync.IService; using TaskAsync.Model; namespace TaskAsync.Service { public class StudentService : IStudentService { /// <summary> /// 新增 方法標注為async /// </summary> /// <param name="entity"></param> /// <returns></returns> public async Task<int> AddPersonAsync(Student entity) { using (AppDbContext dbContext = new AppDbContext()) { dbContext.Students.Add(entity); // 調用異步方法 int count = await dbContext.SaveChangesAsync(); return count; } } /// <summary> /// 刪除 /// </summary> /// <param name="id"></param> /// <returns></returns> public async Task<int> DeleteByIdAsync(int id) { using (AppDbContext dbContext = new AppDbContext()) { Student student =await dbContext.Students.FindAsync(new object[] { id }); if(student!=null) { dbContext.Students.Remove(student); return await dbContext.SaveChangesAsync(); } else { return 0; } } } public async Task<IEnumerable<Student>> GetAllAsync() { List<Student> list = await Task.Run<List<Student>>(() => { using (AppDbContext dbContext = new AppDbContext()) { return dbContext.Students.ToList(); } }); return list; } public async Task<Student> GetStudentByIdAsync(int id) { using (AppDbContext dbContext = new AppDbContext()) { Student student = await dbContext.Students.FindAsync(new object[] { id }); if (student != null) { return student } else { return null; } } } public async Task<int> UpdateAsync(Student entity) { using (AppDbContext dbContext = new AppDbContext()) { Student student = await dbContext.Students.FindAsync(new object[] { entity.Id }); if (student != null) { student.Name = entity.Name; student.Age = entity.Age; student.Gender = entity.Gender; dbContext.Entry(student).State = System.Data.Entity.EntityState.Modified; return await dbContext.SaveChangesAsync(); } else { return 0; } } } } }
注意:這里同樣需要添加到EntityFramework的引用。
6、添加控制器
我們在ASP.NET MVC項目里面首先添加對上面幾個類庫的引用。
為了測試方法,我們直接添加一個包含視圖的MVC5控制器(使用Entity Framework),這樣就會自動生成UI界面了,如下圖所示:
模型類選擇Student,數據上下文類選擇AppDbContext,如下圖所示:
創建完成之后,會看到自動添加了視圖:
控制器里也自動生成了代碼:
using System.Data.Entity; using System.Linq; using System.Net; using System.Web.Mvc; using TaskAsync.Data; using TaskAsync.Model; namespace TaskAsync.UI.Controllers { public class StudentController : Controller { private AppDbContext db = new AppDbContext(); // GET: Student public ActionResult Index() { return View(db.Students.ToList()); } // GET: Student/Details/5 public ActionResult Details(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); } // GET: Student/Create public ActionResult Create() { return View(); } // POST: Student/Create // 為了防止“過多發布”攻擊,請啟用要綁定到的特定屬性,有關 // 詳細信息,請參閱 https://go.microsoft.com/fwlink/?LinkId=317598。 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "Id,Name,Age,Gender")] Student student) { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } return View(student); } // GET: Student/Edit/5 public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); } // POST: Student/Edit/5 // 為了防止“過多發布”攻擊,請啟用要綁定到的特定屬性,有關 // 詳細信息,請參閱 https://go.microsoft.com/fwlink/?LinkId=317598。 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "Id,Name,Age,Gender")] Student student) { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(student); } // GET: Student/Delete/5 public ActionResult Delete(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); } // POST: Student/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public ActionResult DeleteConfirmed(int id) { Student student = db.Students.Find(id); db.Students.Remove(student); db.SaveChanges(); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } } }
但是框架生成的代碼都是同步方法的,不是我們需要的,我們改成異步的方法:
using System.Data.Entity; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web.Mvc; using TaskAsync.Data; using TaskAsync.IService; using TaskAsync.Model; using TaskAsync.Service; namespace TaskAsync.UI.Controllers { public class StudentController : Controller { //private AppDbContext db = new AppDbContext(); IStudentService service = new StudentService(); // GET: Student public async Task<ActionResult> Index() { return View(await service.GetAllAsync()); } // GET: Student/Details/5 public async Task<ActionResult> Details(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student =await service.GetStudentByIdAsync((int)id); if (student == null) { return HttpNotFound(); } return View(student); } // GET: Student/Create public ActionResult Create() { return View(); } // POST: Student/Create // 為了防止“過多發布”攻擊,請啟用要綁定到的特定屬性,有關 // 詳細信息,請參閱 https://go.microsoft.com/fwlink/?LinkId=317598。 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create([Bind(Include = "Id,Name,Age,Gender")] Student student) { if (ModelState.IsValid) { int count = await service.AddPersonAsync(student); if(count>0) { return RedirectToAction("Index"); } } return View(student); } // GET: Student/Edit/5 public async Task<ActionResult> Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = await service.GetStudentByIdAsync((int)id); if (student == null) { return HttpNotFound(); } return View(student); } // POST: Student/Edit/5 // 為了防止“過多發布”攻擊,請啟用要綁定到的特定屬性,有關 // 詳細信息,請參閱 https://go.microsoft.com/fwlink/?LinkId=317598。 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit([Bind(Include = "Id,Name,Age,Gender")] Student student) { if (ModelState.IsValid) { int count = await service.UpdateAsync(student); if (count > 0) { return RedirectToAction("Index"); } } return View(student); } // GET: Student/Delete/5 public async Task<ActionResult> Delete(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Student student = await service.GetStudentByIdAsync((int)id); if (student == null) { return HttpNotFound(); } return View(student); } // POST: Student/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<ActionResult> DeleteConfirmed(int id) { int count = await service.DeleteByIdAsync(id); return RedirectToAction("Index"); } //protected override void Dispose(bool disposing) //{ // if (disposing) // { // db.Dispose(); // } // base.Dispose(disposing); //} } }
然后我們在修改_Layout.cshtml視圖文件,添加學生管理的一個標簽:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - 我的 ASP.NET 應用程序</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> @Html.ActionLink("應用程序名稱", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" }) </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li>@Html.ActionLink("主頁", "Index", "Home")</li> <li>@Html.ActionLink("關於", "About", "Home")</li> <li>@Html.ActionLink("聯系方式", "Contact", "Home")</li> <li>@Html.ActionLink("學生管理", "Index", "Student")</li> </ul> </div> </div> </div> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>© @DateTime.Now.Year - 我的 ASP.NET 應用程序</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html>
運行程序,點擊“學生管理”標簽,就可以看到列表數據了:
這樣我們就完成了一個ASP.NET MVC+EF實現異步增刪改查的方法了。 最終項目結構:
GitHub代碼地址: git@github.com:JiangXiaoLiang1988/TaskAsync.git。