2013年10月29日 星期二

初探 Entity Framework 6 的 Async/Await 功能

Entity Framework 6 其中一個新的功能就是有支援 Async/Await 功能,EF6 加入了許多非同步的方法,這些新的非同步方法會在方法名稱加上「Async」後綴詞,例如:FirstAsync, FirstOrDefaultAsync,  SumAsync, MinAsync, MaxAsync 等等。

這一篇文章只是看看 EF6 所提供的 Async.Await 功能要如何使用,先以讀取資料的操作來說明,而新增、刪除、修改等操作就留待下一篇再做說明。

 


2013-10-29 更正:

有關使用非同步處理的執行結果會快於沒有使用非同步處理,再重新對程式做檢查之後發現到一個問題點,因為這個問題而導致執行結果產生這麼大的出入,對於未經再三檢查而產生錯誤的結果,在此向各位致歉,並且將錯誤內容做出更正。

 

開發環境:Windows 8.1

開發工具:Visual Studio 2013

資料庫:MS SQL Server 2008 R2

 

建立一個新的 ASP.NET MVC 5 網站(不加入驗證)

image

新建立好的 ASP.NET MVC 網站並不會加入 EntityFramework 的參考,所以要使用 NuGet 加入 EntityFramework 6 的參考,

image

這次不用 LocalDB 而是直接連結 MS SQL Server 2008 R2 的 Northwind 資料庫,

image

 

完成以上的基礎建設之後,現在要來增加程式實做的部份,我這邊並不會在某個 Controller 裡直接對 DbContext 做資料存取操作,而是如同我之前的範例程式(ex: 分層架構系列)先建立 IRepository 以及實做的 GenericRepository,

這一篇文章暫時只會說明讀取資料的操作,所以就先不建立其餘的新增刪除修改方法,

image

Async 不支援 IQueryable<T> 所以這邊只有先對取得一筆資料的 GetAsync 方法做非同步操作,該方法所傳的類別為 Task<TEntity>,Task<T> 其命名空間為「System.Threading.Tasks」。

接著看 GetAsync 實作的程式內容,

image

在上面可以看到 GetAsync 方法特別標示的幾個地方,在方法的簽名中要加上 async 關鍵字,方法的回傳值簽名為 Task<TEntity>,然後在最後傳回值前加上 await 關鍵字。

MSDN – async

使用 async 修飾詞,您可以指定方法、Lambda 運算式匿名方法是非同步的。如果您在方法或運算式上使用這個修飾詞,就稱為非同步方法。

MSDN - await

await 使用的非同步方法必須以 async 關鍵字修改。 這種方法,透過使用 async 修飾詞來定義和通常包含一或多個 await 運算式,稱為「非同步方法」(async method)。

 

完整的 GenericRepository 內容如下:

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 BlogSample.Models;
 
namespace BlogSample.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;
        }
 
 
 
        public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await this.dbSet.FirstOrDefaultAsync(predicate);
        }
 
        public IQueryable<TEntity> GetAll()
        {
            return this.dbSet.AsQueryable();
        }
 
        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 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;
                }
            }
        }
    }
}

 

接著在 Controllers 目錄下新建立一個 OrderController,這邊所選擇的是「MVC  5 控制器 - 空白」,

image

然後在 OrderController 加入程式,取得顯示全部 Order 資料的內容,

image

新增檢視

SNAGHTML1156dacd

執行結果

image

 

回到 OrderController,在這裡我們加入一個使用 async 方法的 Action 方法,我們檢視單筆 Order 資料時,可以透過已經建立好的 GenericRepository 的 GetAsync() 方法來取得資料,不過 Controller 的 Action 要使用非同步的方法就必須也要在方法簽名加上 async 關鍵字,而透過非同步方法取得資料也需要加上 await 關鍵字,而且原本 ActionResult 也需要修改為 Task<ActionResult>,實作的程式內容如下:

image

新增 Details 檢視

SNAGHTML115dfcca

執行結果

image

 

Collection 的非同步處理

上面是取得單一筆資料時的非同步操作處理方式,但如果我們想要對一堆資料也做非同步處理時應該怎麼做呢?前面有講過非同步處理無法對 IQueryable<T> 進行處理,但我們可以對 List<T> 來做非同步操作,而 EntityFramework 的 QureryableExtensions 也有 ToListAsync() 的擴充方法,

image

首先對 IRepository 進行修改,這邊特別要注意到,只要是執行非同步處理的方法,方法名稱要加上「Async」後綴詞以示區別,

image

再來修改 GenericRepository 的內容,

image

完成 IRepository 與 GenericRepository 的修改之後,接下來就是在 OrderController 裡的使用,只要將原本的 Index Action 方法做以下的修改就可以了,

image

 

這邊我用 Firebug 的 Network 所紀錄的結果來做兩者執行的比較(各取執行第一次與第三次的執行結果)

不使用非同步處理

image

image

 

有使用非同步(async/await)處理

image

image

可以看到沒有使用以及有使用非同步處理的執行結果兩者是差不多,在我多次測試的結果看來,沒有使用非同步的第一次執行是會比有使用非同步的要快一些,但是在第二次之後的執行,則是有使用非同步的執行會比沒有使用非同步的要快許多。

檢查程式過後,原本 GenericRepository 裡的 GetAllWithIncludesAsync() 方法最後傳回的結果是沒有加入 includes,以至於發生非同步查詢結果的執行時間快於沒有使用非同步處理的結果,所以程式經過修改之後,將兩者執行的結果再做重新紀錄與更新。

 


這一篇就說到這邊,有關非同步處理的新增、刪除、修改就留待之後再說明。

 

延伸閱讀:

EF6 RTM Available - ADO.NET Blog

MSDN - 使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)

MSDN – Data Developer Center - Entity Framework Async Query & Save (EF6 onwards)

 

以上

3 則留言:

  1. 為什麼快那麼多啊?

    Index Action 只有對 Order Table 取資料一個動作而已,沒有很多複雜的動作,這樣有沒有使用非同步, 應該是差不多才對啊 ??

    回覆刪除
    回覆
    1. 是的,因為程式內容有個地方出現問題,因而導致錯誤的結果,在此說聲抱歉。
      經過程式的修改後的再三確認,兩者執行的時間的確是差不多。

      刪除
    2. 不會啦,是因為想不通,所以才發問請教一下 ^^
      Entity Framework 6 支援非同步,是另人很高興的一件事,存取多個 Table 時,相信就會有很大的幫助了

      刪除

提醒

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