異步查詢並儲存
注意
僅限 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 儲存至資料庫,然後從資料庫擷取所有部落格,並將其列印至控制台。 之後,程式會將當天 的報價寫入主控台。
由於程式代碼是同步的,因此我們可以在執行程式時觀察下列執行流程:
SaveChanges
開始將新的 部落格 推送至資料庫SaveChanges
完成- 所有部落格的查詢都會傳送至資料庫
- 查詢傳回和結果會 寫入主控台
- 當天的報價會 寫入主控台
讓它成為異步
既然我們已經啟動並執行程式,我們可以開始使用新的異步和 await 關鍵詞。 我們已對 Program.cs 進行下列變更
- 第 2 行:命名空間的
System.Data.Entity
using 語句可讓我們存取 EF 異步擴充方法。 - 第 4 行:命名空間的
System.Threading.Tasks
using 語句可讓我們使用Task
類型。 - 第 12 和 18 行:我們會擷取為工作,以監視 (第 12 行) 的進度
PerformSomeDatabaseOperations
,然後封鎖此工作的程式執行,一旦程式的所有工作完成(第 18 行)。 - 第 25 行:我們已更新
PerformSomeDatabaseOperations
為 ,async
並傳回Task
。 - 第 35 行:我們現在呼叫 的
SaveChanges
Async 版本,並等候其完成。 - 第 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);
}
}
}
}
}
既然程式代碼是異步的,我們可以在執行程式時觀察不同的執行流程:
SaveChanges
開始將新的 部落格 推送至資料庫
一旦命令傳送至資料庫,目前 Managed 線程就不再需要計算時間。PerformDatabaseOperations
方法會傳回 (即使尚未完成執行),而且 Main 方法中的程式流程仍會繼續。- 當天的報價會寫入主控台
由於Main方法中沒有其他工作要做,因此在資料庫作業完成之前,Managed 線程會在呼叫上Wait
封鎖。 完成後,將會執行其餘PerformDatabaseOperations
部分。 SaveChanges
完成- 所有部落格的查詢都會傳送至資料庫
同樣地,在資料庫中處理查詢時,受控線程可以自由執行其他工作。 由於所有其他執行都已完成,線程只會在等候呼叫時停止。 - 查詢傳回和結果會 寫入主控台
外賣
我們現在看到使用 EF 的異步方法是多麼容易。 雖然異步的優點在簡單的控制台應用程式中可能並不明顯,但在長時間執行或網路系結活動可能會封鎖應用程式,或造成大量線程增加記憶體使用量的情況下,可以套用這些相同的策略。