2013年11月8日 星期五

初探 Entity Framework 6 - Async/Await Create, Read, Update, Delete with MVC 5

初探 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> 只有讀取資料的方法,

image

現在我們再新增三個方法,分別為 Create, Update, Delete,而且都是要使用非同步處理,

image

IRepository<TEntity> 的全貌

image

 

接著我們要在 GenericRepository<TEntity> 裡實做新增的三個方法,

image

 

這裡我另外建立的一個 Model「Member」,接下來將使用這個 Model 來建立 Controller 以及使用非同步處理 CRUD 操作,

image

image

 

建立 Controller

在「添加基架」的視窗裡,選擇「具有檢視、使用 Entity Framework 的 MVC 5 控制器」

image

接著在「加入控制器」視窗裡要勾選「使用非同步控制器動作」,記得模型類別是選擇「Member」,

SNAGHTML9b53282

完成控制器的加入操作後,會建立好 MemberController 內容以及檢視頁面,

image

image

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 兩個方法,都是做同一個資料讀取的操作,差別只在於是否使用非同步處理,

image

image

 

補充 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)

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。