您好,登錄后才能下訂單哦!
本篇內容主要講解“.net EF Core專題:EF Core 讀取數據是如何運行的”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“.net EF Core專題:EF Core 讀取數據是如何運行的”吧!
本文將為你詳細描繪 EF Core 從數據庫中讀取數據的“幕后”視圖。我將揭開兩種數據庫讀取方式的面紗:一個是普通的查詢,另一個是使用 AsNoTracking 方法的非跟蹤查詢。我還將通過一個實驗來演示我是如何解決我的一個客戶遇到的性能問題。
我假設你對 EF Core 已經有了一定的認識,但在深入學習之前,我們先來了解一下如何使用 EF Core,以確保我們已經掌握了一些基本知識。這是一個“深入研究”的課題,所以我準備大量的技術細節,希望我的描述方式你能理解。
本文是“深入理解 EF Core”系列中的第一篇。以下是本系列文章列表:
當 EF Core 從數據庫讀取數據時發生了什么?(本文)
當 EF Core 寫入數據到數據庫時發生了什么?(敬請期待)
概要
EF Core 有兩種方法從數據庫中讀取數據(也稱為查詢):普通 LINQ 查詢和包含 AsNoTracking 方法的非跟蹤 LINQ 查詢。
這兩種方法查詢的返回類(被稱為實體類),它連接的其它的實體類(即所謂的導航屬性)也被同時加載,但這兩種法如何連接及連接的內容是不一樣的。
普通查詢接受的是 DbContext 執行讀取時所有數據的副本——此時的實體類稱為被跟蹤。這允許加載的實體類參與數據庫的更新操作。
普通查詢還會有一些其它的復雜底層實現,稱為關系修補(fixup),用于描述讀入的實體類和其他被跟蹤實體之間的連接關系。
AsNoTracked 非跟蹤查詢沒有副本,所以它沒有被跟蹤——這意味著它比普通查詢更快。這也意味著它不會用于數據庫的寫操作。
最后,我將展示 EF Core 普通查詢中一個鮮為人知的特性,以此作為示例,說明通過導航屬性連接實體類的關系是多么智能。
EF Core 如何讀取數據庫數據
提示:如果你已經對 EF Core 有一定的認識,那么你可以跳過這一節,這部分只是一個如何讀取數據庫的例子。
為了能讓你更好地理解,我先描述一個數據庫結構,然后再給出一個簡單的數據庫讀取示例。下面是一些基本表的結構和它們之間的關系。
這些表被映射到具有類似名稱的類,例如 Book、BookAuthor、Author,這些類的屬性名稱與表的字段名稱相同。由于篇幅有限,我不打算展開來講這些類,但您可以在我的 GitHub 倉庫[1]中查看這些類。
EF Core 讀取數據庫需要下面五部分:
數據庫服務器,如 SQL server, Sqlite, PostgreSQL 等。
具有數據的數據庫。
映射到數據表的類(稱為實體類)。
一個繼承 DbContext 的類,該類包含 EF Core 的配置。
最后,從數據庫讀取數據的命令。
下面的單元測試代碼來自我的 GitHub 創庫[2],展示了一個簡單的示例,它從現有數據庫中讀取 4 個 Book 實體及其關聯的 BookAuthor 和 Authors 實體。
[Fact] public void TestBookCountAuthorsOk() { //SETUP var options = SqliteInMemory.CreateOptions<EfCoreContext>(); //code to set up the database with four books, two with the same Author using (var context = new EfCoreContext(options)) { //ATTEMPT var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList(); //VERIFY books.Count.ShouldEqual(4); books.SelectMany(x => x.AuthorsLink.Select(y => y.Author)) .Distinct().Count().ShouldEqual(3); } }
現在,如果我們將單元測試代碼對應到上面的 5 部分,結果是這樣的:
數據庫服務器——第 5 行:我選擇了一個 Sqlite 數據庫服務器,在本例中是 SqliteInMemory.CreateOptions
方法,它使用我的一個 NuGet 包 EfCore.TestSupport 創建了一個內存數據庫(內存中的數據庫對于單元測試非常有用,因為你可以為這個測試建立一個新的空數據庫)。
具有數據的數據庫——第 6 行:我將在下一篇文章介紹數據是如何寫入數據庫的,現在假設有一個數據庫包含 4 本書信息,其中兩本書的作者是同一個人。
實體類——代碼里這里沒有展示,但是你可以在這里查看這些類[1]。其中有一個 Books 實體類,通過一個名為 BookAuhor 的實體類多對多關聯 Authors 實體類。
一個繼承 DbContext 的類——第 7 行:EfCoreContext 類繼承了 DbContext 類并配置了從類到數據庫的映射關系(你可以在我的 GitHub 倉庫[3] 中查看該類)。
從數據庫讀取數據的命令——第 10 到 13 行,這是一個查詢:
第 10 行 — context 為 EfCoreContext 的實例,通過它訪問你的數據庫,.Books
表示您希望訪問 Books 表。
第 11 行 — Include 被稱為貪婪加載,它告訴 EF Core 當它加載 Books 時,也應該加載關聯到的所有 BookAuthor 實體類。
第 12 行 — ThenInclude 是繼續貪婪加載,它告訴 EF Core 當它加載一個 BookAuthor 時,它也應該加載關聯到該 BookAuthor 的 Author 實體類。
所有這一切查詢出來是一個結果集,其中有普通屬性,像 Books 的 Title 屬性;有關聯實體類的導航屬性,像 Books 的 AuthorsLink 屬性。
這個示例稱為查詢或讀取,也是四種數據庫訪問類型之一,即 CRUD(新增、讀取、更新和刪除)。我將在下一篇文章中介紹新增和更新。
EF Core 如何表示讀取的數據
當你查詢數據庫時,EF Core 會將數據庫返回的數據轉換為實體類并填充導航屬性的值。在本節中,我們將研究兩種類型的查詢步驟——普通查詢(即沒有 AsNoTracking 方法,也稱為讀寫查詢)和添加了 AsNoTracking 方法的非跟蹤查詢(稱為只讀查詢)。
我們先來看一下最初 LINQ 語句是如何轉換成數據庫相應的查詢命令然后返回數據的。對于我們將要看到的兩種類型的查詢來說,這是很常見的操作。關于查詢的第一部分,請參見下圖。
有一些非常復雜的代碼將你的 LINQ 轉換為數據庫查詢命令,但這些內部細節我們不必關心。如果你的 LINQ 不能被翻譯,你會從 EF Core 得到一個異常消息,其中包含類似“不能被翻譯”的描述詞語。此外,當數據返回時,像 Value Converters[4] 這樣的特性可能會調整數據。
本節展示了查詢的第一部分,其中 LINQ 被轉換為數據庫命令并返回所有正確的值。現在我們來看查詢的第二部分,在這里 EF Core 獲取返回值并將它們轉換為實體類的實例,并填充導航屬性。我們將分別看看兩種類型的查詢。
1. 普通查詢(讀寫查詢)
普通查詢讀取數據的方式可以修改數據并更新到數據庫,這就是我將其稱為讀寫查詢的原因。它不會自動更新數據(請參閱下一篇文章,了解如何寫入數據庫)。如果你要更新數據,你的查詢必須是讀寫查詢。
我在介紹中給出的示例執行的是一個普通讀寫查詢,讀取帶有 AuthorsLink 實例的示例。下面是該示例的查詢部分的代碼:
var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
然后 EF Core 通過三個步驟將這些值轉換并填充含有導航屬性的實體類。下圖顯示了這三個步驟以及生成的實體類及其導航屬性的實體類。
讓我們來分析一下這三個步驟:
創建類并填充數據。它接受數據庫返回的值,并填充非導航(稱為標量)屬性、字段等。在 Book 實體類中,是 BookId(主鍵)、Title 等屬性——參見上圖左下角淺藍色矩形。
修補關聯關系。首先是填入主鍵和外鍵的信息,它們定義如何相互關聯數據。然后,EF Core 使用這些鍵設置實體類之間的導航屬性(如圖中藍色粗線所示)。這個關系的修補所需的信息不僅是查詢讀入的實體類,它還會查看 DbContext 中跟蹤的每個實體,并填充導航屬性。這是一個強大的功能,但你的被跟蹤實體越多,所需消耗時間也越多——這就是為什么需要 AsNoTracking 來實現更快的查詢。
創建跟蹤快照。跟蹤快照是返回給用戶的實體類的一個副本,加上它所隱藏的與每個實體類的關聯關系——若一個實體處于被跟蹤狀態,這意味著它將會發生修改并會寫入到數據庫中。
2. 非跟蹤查詢(只讀查詢)
非跟蹤查詢,即使用 AsNoTracking 方法的查詢,是一個只讀查詢。這意味著,當 SaveChanges 方法被調用時,你讀取的任何內容都不會被寫入數據庫。非跟蹤查詢的查詢效率更高,在下一節中,我將介紹非跟蹤查詢以及與普通查詢的其他區別。
在前文的示例之后,我修改了查詢代碼,添加了下面的 AsNoTracking 方法(請看第 2 行):
var books = context.Books .AsNoTracking() .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
這里的 LINQ 查詢只有上面的普通查詢的前兩個步驟(沒有第三個步驟)。下圖顯示了 AsNoTracking 查詢的步驟。
步驟如下:
創建類并填充數據。它接受數據庫返回的值,并填充非導航(稱為標量)屬性、字段等。在 Book 實體類中,是 BookId(主鍵)、Title 等屬性——參見上圖左下角淺藍色矩形。
修補關聯關系。首先是填入主鍵和外鍵的信息,它們定義如何相互關聯數據。然后,EF Core 使用這些鍵設置實體類之間的導航屬性(如圖中藍色粗線所示)。這個關系的修補所需的信息不僅是查詢讀入的實體類,它還會查看 DbContext 中跟蹤的每個實體,并填充導航屬性。這是一個強大的功能,但你的被跟蹤實體越多,所需消耗時間也越多——這就是為什么需要 AsNoTracking 來實現更快的查詢。
普通查詢和非跟蹤查詢的區別
現在讓我們比較這兩種查詢比較明顯的區別。
非跟蹤查詢查詢的性能更好。使用非跟蹤查詢查詢的主要原因是性能。非跟蹤查詢查詢表現為:
稍微快一點,使用的內存稍微少一點,因為它不需要創建跟蹤快照。
避免沒有必要的跟蹤快照可以提高 SaveChanges 的性能,因為它不必檢查跟蹤快照以查找更改。
稍微快一點,因為修補關聯關系時沒有所謂的身份解析。這就是為什么你會得到兩個具有相同數據的 Author 實例。
非跟蹤查詢修補關聯關系時只鏈接查詢中的實體。在普通查詢中,我已經說過修補關聯關系時連接的是查詢中的實體和當前跟蹤的實體,但是非跟蹤查詢只修補查詢中的實體關系。
非跟蹤查詢并不總是代表數據庫關系。這兩種類型查詢之間的關系修補的另一個區別是,非跟蹤查詢關系修補更快,它不需要標識的解析。這可以為數據庫中的同一行生成多個實例——見上圖右下角藍色的 Author 實體和注釋。如果只是向用戶顯示數據,那么這種差異并不重要,但是如果具有業務邏輯,那么多個實例不能正確反映數據的結構,就可能會有問題。
對層級數據有用的關系修補特性
關聯關系修補的步驟是非常智能的,特別是在普通查詢中。下面我想向你展示我是如何利用關系修補的特性來解決一個客戶項目中的性能問題的。
我曾在一家公司工作,那里的許多數據處理都是層次化結構的,即數據具有一系列深度不確定的關聯關系。問題是我必須先解析整個層次結構,然后才能呈現這些數據。我最初是通過貪婪的方式加載前兩個層級,然后顯式地加載更深的層級來實現這一點的。它可以工作,但是性能非常慢,并且數據庫因大量單數據庫訪問而超載。
這不得不讓我思考解決辦法,如果普通查詢的關系修補那么智能的話,它能幫助我提高查詢的性能嗎?它可以!讓我給你舉一個公司員工的例子。下圖顯示了我們想要加載的公司的層次結構。
你可以接龍式地使用 .Include(x => x.WorksForMe).ThenInclude(x => x.WorksForMe)… 等等來加載所需的層級信息,但結果是一個 .Include(x => x.WorksForMe) 就夠了。因為 EF Core 的關系修補為你做了剩下的事情,這一點很驚奇,但也很有用。
例如,如果我想查詢角色為 Development 的所有員工(每個員工都有一個名為 WhatTheyDo 的屬性和名為 Role 的屬性,該 Role 包含他們工作的部門),我可以這樣編寫代碼:
var devDept = context.Employees .Include(x => x.WorksFromMe) .Where(x => x.WhatTheyDo.HasFlag(Roles.Development)) .ToList();
這將創建一個查詢,用于加載角色為 Development 的所有員工,并且在員工實體類上修補與 WorksFoMe 導航屬性(集合)和 Manager 導航屬性(單個)的關系。通過只執行一個查詢,既提高了查詢花費的時間,又減少了數據庫服務器上的負載。
總結
你已經看到了兩種類型的查詢,我稱之為 a)普通的讀寫查詢,和 b) 非跟蹤的只讀查詢。對于每一種查詢類型,我都向你展示了 EF Core “幕后”是如何讀取數據并展示的。他們工作方式的不同也表現出他們的優勢和劣勢。
非跟蹤查詢是只讀查詢的解決方案,因為它比普通讀寫查詢更快。但是您應該記住關系修補的機制,它可以在數據庫只有一個關系的情況下創建類的多個實例。
普通的讀寫查詢是查詢跟蹤實體的解決方案,這意味著你可以在創建、更新和刪除數據時使用它們。普通的讀寫查詢確實會占用更多的時間和內存資源,但是有一些有用的特性,比如自動鏈接到其他被跟蹤的實體類實例。
到此,相信大家對“.net EF Core專題:EF Core 讀取數據是如何運行的”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。