初探 Entity Framework 6 前面已經說明過幾個新功能:
初探 Entity Framework 6 的 Async/Await 功能
初探 Entity Framework 6 – Logging
初探 Entity Framework 6 - Intercepting Part.1
初探 Entity Framework 6 - Intercepting Part.2
而第一篇只有針對資料讀取的部份說明而已,還沒有對新增、修改、刪除來做說明,但其實非同步的新增修改刪除並沒有什麼特別的地方,所以這一篇就以 ASP.NET MVC 5 Controller 開始來說明如何將 Controller 裡基本的 CRUD 使用非同步的方式來做處理。
在之前我們所建立的 IRepository<TEntity> 只有讀取資料的方法,
現在我們再新增三個方法,分別為 Create, Update, Delete,而且都是要使用非同步處理,
IRepository<TEntity> 的全貌
接著我們要在 GenericRepository<TEntity> 裡實做新增的三個方法,
這裡我另外建立的一個 Model「Member」,接下來將使用這個 Model 來建立 Controller 以及使用非同步處理 CRUD 操作,
建立 Controller
在「添加基架」的視窗裡,選擇「具有檢視、使用 Entity Framework 的 MVC 5 控制器」
接著在「加入控制器」視窗裡要勾選「使用非同步控制器動作」,記得模型類別是選擇「Member」,
完成控制器的加入操作後,會建立好 MemberController 內容以及檢視頁面,
using System;using System.Collections.Generic;using System.Data;using System.Data.Entity;using System.Linq;using System.Threading.Tasks;using System.Net;using System.Web;using System.Web.Mvc;using Async.Models;namespace Async.Controllers{public class MemberController : Controller
    {private NorthwindEntities db = new NorthwindEntities();
        // GET: /Member/        public async Task<ActionResult> Index()        {            return View(await db.Members.ToListAsync());}
        // GET: /Member/Details/5        public async Task<ActionResult> Details(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Member member = await db.Members.FindAsync(id);
if (member == null)
            {                return HttpNotFound();}
            return View(member);}
        // GET: /Member/Create        public ActionResult Create()        {            return View();}
        // POST: /Member/Create        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include="ID,Name,Gender,BirthDate,CreateDate,UpdateDate")] Member member)
        {            if (ModelState.IsValid)            {member.ID = Guid.NewGuid();
db.Members.Add(member);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
            return View(member);}
        // GET: /Member/Edit/5        public async Task<ActionResult> Edit(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Member member = await db.Members.FindAsync(id);
if (member == null)
            {                return HttpNotFound();}
            return View(member);}
        // POST: /Member/Edit/5        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include="ID,Name,Gender,BirthDate,CreateDate,UpdateDate")] Member member)
        {            if (ModelState.IsValid)            {db.Entry(member).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
            return View(member);}
        // GET: /Member/Delete/5        public async Task<ActionResult> Delete(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Member member = await db.Members.FindAsync(id);
if (member == null)
            {                return HttpNotFound();}
            return View(member);}
        // POST: /Member/Delete/5        [HttpPost, ActionName("Delete")][ValidateAntiForgeryToken]
        public async Task<ActionResult> DeleteConfirmed(Guid id)        {Member member = await db.Members.FindAsync(id);
db.Members.Remove(member);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
        {            if (disposing)            {db.Dispose();
}
            base.Dispose(disposing);}
}
}
修改 Controller
不過我們要置換 MemberController 裡的程式,將原本 Action 方法內直接對 EntityFramework 存取的部份換成使用 GenericRepository<TEntity>,修改後並且使用非同步處理的 MemberController 如下:
using System;using System.Net;using System.Threading.Tasks;using System.Web.Mvc;using Async.Models;using Async.Repository;namespace Async.Controllers{public class MemberController : Controller
    {private IRepository<Member> memberRepository = new GenericRepository<Member>();
        public async Task<ActionResult> Index()        {var result = await memberRepository.GetAllAsync();
            return View(result);}
        public async Task<ActionResult> Details(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
            Member member = await this.memberRepository.GetAsync(x => x.ID == id.Value);if (member == null)
            {                return HttpNotFound();}
            return View(member);}
        public ActionResult Create()        {            return View();}
[HttpPost]
[ValidateAntiForgeryToken]
        public async Task<ActionResult> Create(            [Bind(Include = "ID,Name,Gender,BirthDate")] Member member)        {            if (ModelState.IsValid)            {member.ID = Guid.NewGuid();
member.CreateDate = DateTime.Now;
member.UpdateDate = DateTime.Now;
                await this.memberRepository.CreateAsync(member);return RedirectToAction("Index");
}
            return View(member);}
        public async Task<ActionResult> Edit(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
            Member member = await this.memberRepository.GetAsync(x => x.ID == id.Value);if (member == null)
            {                return HttpNotFound();}
            return View(member);}
[HttpPost]
[ValidateAntiForgeryToken]
        public async Task<ActionResult> Edit(            [Bind(Include = "ID,Name,Gender,BirthDate")] Member member)        {            if (ModelState.IsValid)            {                Member original = await this.memberRepository.GetAsync(x => x.ID == member.ID);original.Name = member.Name;
original.Gender = member.Gender;
original.BirthDate = member.BirthDate;
original.UpdateDate = DateTime.Now;
                await this.memberRepository.UpdateAsync(original);return RedirectToAction("Index");
}
            return View(member);}
        public async Task<ActionResult> Delete(Guid? id)        {if (id == null)
            {return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
            Member member = await this.memberRepository.GetAsync(x => x.ID == id.Value);if (member == null)
            {                return HttpNotFound();}
            return View(member);}
        [HttpPost, ActionName("Delete")][ValidateAntiForgeryToken]
        public async Task<ActionResult> DeleteConfirmed(Guid id)        {            Member member = await this.memberRepository.GetAsync(x => x.ID == id);            await this.memberRepository.DeleteAsync(member);return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
        {            if (disposing)            {                this.memberRepository.Dispose();}
            base.Dispose(disposing);}
}
}
Entity Framework 6 直接提供了非同步處理的功能,這篇文章以其之前的「初探 Entity Framework 6 的 Async/Await 功能」直接跟大家說明如何在 Repository 裡建立非同步處理的方法,其實這就跟以前把 Controller 裡直接對 EF 進行資料存取操作的部份抽出來放在 Repository 的方式是一樣的,不一樣的地方在於如果要使用非同步處理就必須做調整,例如 Controller 的 Action 方法就必須要將方法簽名增加 async 關鍵字,然後 ActionResult 要修改為 Task<ActionResult>。
另外在 IRepository 以及 GenericRepository 的實作上,不需要使用非同步處理去取代原本沒有使用非同步處理的方法,兩者可以並存,在需要使用非同步處理或不需要使用的時候就可以做調正,例如原本的 GetAll 與 GetAllAsync 兩個方法,都是做同一個資料讀取的操作,差別只在於是否使用非同步處理,
補充 IRepository 與 GenericRepository 內容
IRepository.cs
using System;using System.Collections.Generic;using System.Linq;using System.Linq.Expressions;using System.Text;using System.Threading.Tasks;namespace Async.Repository{public interface IRepository<TEntity> : IDisposable
where TEntity : class
    {Task CreateAsync(TEntity instance);
Task UpdateAsync(TEntity instance);
Task DeleteAsync(TEntity instance);
        TEntity Get(Expression<Func<TEntity, bool>> predicate);        Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate);Task<TEntity> GetWithIncludeAsync(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes);
IQueryable<TEntity> GetAll();
Task<List<TEntity>> GetAllAsync();
IQueryable<TEntity> GetAllWithInclude(params Expression<Func<TEntity, object>>[] includes);
Task<List<TEntity>> GetAllWithIncludeAsync(params Expression<Func<TEntity, object>>[] includes);
        IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);IQueryable<TEntity> FindWithInclude(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes);
}
}
GenericRepository.cs
using System;using System.Collections.Generic;using System.Data.Entity;using System.Diagnostics;using System.Linq;using System.Linq.Expressions;using System.Threading.Tasks;using System.Web;using Async.Models;namespace Async.Repository{public class GenericRepository<TEntity> : IRepository<TEntity>
where TEntity : class
    {        private DbContext _context        {get;
set;
}
        private DbSet<TEntity> dbSet        {get
            {return this._context.Set<TEntity>();
}
}
        public GenericRepository(): this(new NorthwindEntities())
        {}
        public GenericRepository(DbContext context)        {if (context == null)
            {throw new ArgumentNullException("context");
}
            this._context = context;            this._context.Database.Log = (log) => Debug.WriteLine(log);}
        public async Task CreateAsync(TEntity instance)        {            this.dbSet.Add(instance);            await this._context.SaveChangesAsync();}
        public async Task UpdateAsync(TEntity instance)        {            this._context.Entry(instance).State = EntityState.Modified;            await this._context.SaveChangesAsync();}
        public async Task DeleteAsync(TEntity instance)        {            this.dbSet.Remove(instance);            await this._context.SaveChangesAsync();}
public TEntity Get(Expression<Func<TEntity, bool>> predicate)
        {return this.dbSet.FirstOrDefault(predicate);
}
public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate)
        {return await this.dbSet.FirstOrDefaultAsync(predicate);
}
public async Task<TEntity> GetWithIncludeAsync(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes)
        {            var query = this.dbSet.AsQueryable();if (includes != null)
            {query = includes.Aggregate(
query,
(current, include) => current.Include(include));
}
var result = query.FirstOrDefaultAsync(predicate);
            return await result;}
        public IQueryable<TEntity> GetAll()        {return this.dbSet.AsQueryable();
}
        public async Task<List<TEntity>> GetAllAsync()        {return await this.dbSet.ToListAsync();
}
public IQueryable<TEntity> GetAllWithInclude(params Expression<Func<TEntity, object>>[] includes)
        {            var query = this.dbSet.AsQueryable();if (includes != null)
            {query = includes.Aggregate(
query,
(current, include) => current.Include(include));
}
            return query;}
public async Task<List<TEntity>> GetAllWithIncludeAsync(params Expression<Func<TEntity, object>>[] includes)
        {            var query = this.dbSet.AsQueryable();if (includes != null)
            {query = includes.Aggregate(
query,
(current, include) => current.Include(include));
}
            return await query.ToListAsync();}
public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {return this.dbSet.Where(predicate);
}
public IQueryable<TEntity> FindWithInclude(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes)
        {            var query = this.dbSet.AsQueryable();if (includes != null)
            {query = includes.Aggregate(
query,
(current, include) => current.Include(include));
}
            return query.Where(predicate);}
public void Dispose()
        {this.Dispose(true);
            GC.SuppressFinalize(this);}
protected virtual void Dispose(bool disposing)
        {            if (disposing)            {if (this._context != null)
                {                    this._context.Dispose();this._context = null;
}
}
}
}
}
如果對於 Async/Await 的概念與操作還尚未了解的話,可以先仔細閱讀以下連結的文件說明,
MSDN - 使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)
以上
沒有留言:
張貼留言