異步查詢並儲存

注意

僅限 EF6 及更新版本 - Entity Framework 6 已引進此頁面中所討論的功能及 API 等等。 如果您使用的是較早版本,則不適用部分或全部的資訊。

EF6 引進了異步查詢的支援,並使用 .NET 4.5 中引進的 async 和 await 關鍵詞 來儲存。 雖然並非所有應用程式都可能受益於異步,但它可以用來改善處理長時間執行、網路或 I/O 系結工作時的用戶端回應性和伺服器延展性。

何時真正使用異步

本逐步解說的目的是要以一種方式引進異步概念,讓您輕鬆地觀察異步程式與同步程式執行之間的差異。 本逐步解說並非用來說明異步程序設計提供優點的任何重要案例。

異步程序設計主要著重於釋放目前的 Managed 線程(執行 .NET 程式代碼的線程)來執行其他工作,同時等候不需要從 Managed 線程執行任何計算時間的作業。 例如,雖然資料庫引擎正在處理查詢,但 .NET 程式代碼不會執行任何動作。

在用戶端應用程式中(WinForms、WPF 等)中,目前的線程可用來在執行異步作業時讓UI保持回應。 在伺服器應用程式中(ASP.NET 等)中,線程可用來處理其他連入要求 -- 這可以減少記憶體使用量和/或增加伺服器的輸送量。

在大部分使用異步的應用程式中,沒有任何明顯的優點,甚至可能有害。 先使用測試、分析和常識來測量特定案例中異步的影響,再認可它。

以下是一些深入瞭解異步的資源:

建立模型

我們將使用 Code First 工作流程 來建立模型併產生資料庫,不過異步功能將會與所有 EF 模型搭配使用,包括使用 EF 設計工具建立的模型。

  • 建立主控台應用程式並呼叫它 AsyncDemo
  • 新增 EntityFramework NuGet 套件
    • 在 方案總管 中,以滑鼠右鍵按兩下 AsyncDemo 專案
    • 選取 [ 管理 NuGet 套件...
    • 在 [管理 NuGet 套件] 對話框中,選取 [ 在線 ] 索引標籤,然後選擇 EntityFramework 套件
    • 按一下 [安裝]
  • 使用下列實作新增 Model.cs 類別
    using System.Collections.Generic;
    using System.Data.Entity;

    namespace AsyncDemo
    {
        public class BloggingContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
            public DbSet<Post> Posts { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }

            public virtual List<Post> Posts { get; set; }
        }

        public class Post
        {
            public int PostId { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }

            public int BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }

 

建立同步程式

既然我們有EF模型,讓我們撰寫一些用來執行某些數據存取的程序代碼。

  • 以下列程式代碼取代 Program.cs 的內容
    using System;
    using System.Linq;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static void PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    db.SaveChanges();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = (from b in db.Blogs
                                orderby b.Name
                                select b).ToList();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" " + blog.Name);
                    }
                }
            }
        }
    }

此程式代碼會呼叫 方法,PerformDatabaseOperations將新的 Blog 儲存至資料庫,然後從資料庫擷取所有部落,並將其列印至控制台。 之後,程式會將當天 的報價寫入主控台

由於程式代碼是同步的,因此我們可以在執行程式時觀察下列執行流程:

  1. SaveChanges 開始將新的 部落格 推送至資料庫
  2. SaveChanges 完成
  3. 所有部落格的查詢都會傳送至資料庫
  4. 查詢傳回和結果會 寫入主控台
  5. 當天的報價會 寫入主控台

Sync Output 

 

讓它成為異步

既然我們已經啟動並執行程式,我們可以開始使用新的異步和 await 關鍵詞。 我們已對 Program.cs 進行下列變更

  1. 第 2 行:命名空間的 System.Data.Entity using 語句可讓我們存取 EF 異步擴充方法。
  2. 第 4 行:命名空間的 System.Threading.Tasks using 語句可讓我們使用 Task 類型。
  3. 第 12 和 18 行:我們會擷取為工作,以監視 (第 12 行) 的進度 PerformSomeDatabaseOperations ,然後封鎖此工作的程式執行,一旦程式的所有工作完成(第 18 行)。
  4. 第 25 行:我們已更新 PerformSomeDatabaseOperations 為 , async 並傳回 Task
  5. 第 35 行:我們現在呼叫 的 SaveChanges Async 版本,並等候其完成。
  6. 第 42 行:我們現在呼叫 的 ToList Async 版本,並在結果上等候。

如需命名空間中 System.Data.Entity 可用擴充方法的完整清單,請參閱 類別 QueryableExtensions您也需要將 新增 using System.Data.Entity 至 using 語句。

    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Threading.Tasks;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var task = PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                task.Wait();

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static async Task PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    await db.SaveChangesAsync();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = await (from b in db.Blogs
                                orderby b.Name
                                select b).ToListAsync();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" - " + blog.Name);
                    }
                }
            }
        }
    }

既然程式代碼是異步的,我們可以在執行程式時觀察不同的執行流程:

  1. SaveChanges 開始將新的 部落格 推送至資料庫
    一旦命令傳送至資料庫,目前 Managed 線程就不再需要計算時間。 PerformDatabaseOperations方法會傳回 (即使尚未完成執行),而且 Main 方法中的程式流程仍會繼續。
  2. 當天的報價會寫入主控台
    由於Main方法中沒有其他工作要做,因此在資料庫作業完成之前,Managed 線程會在呼叫上 Wait 封鎖。 完成後,將會執行其餘 PerformDatabaseOperations 部分。
  3. SaveChanges 完成
  4. 所有部落格的查詢都會傳送至資料庫
    同樣地,在資料庫中處理查詢時,受控線程可以自由執行其他工作。 由於所有其他執行都已完成,線程只會在等候呼叫時停止。
  5. 查詢傳回和結果會 寫入主控台

Async Output 

 

外賣

我們現在看到使用 EF 的異步方法是多麼容易。 雖然異步的優點在簡單的控制台應用程式中可能並不明顯,但在長時間執行或網路系結活動可能會封鎖應用程式,或造成大量線程增加記憶體使用量的情況下,可以套用這些相同的策略。