溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

發(fā)布時間:2021-03-08 14:32:58 來源:億速云 閱讀:270 作者:TREX 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的”吧!

本文將為你詳細描繪 EF Core 從數(shù)據(jù)庫中讀取數(shù)據(jù)的“幕后”視圖。我將揭開兩種數(shù)據(jù)庫讀取方式的面紗:一個是普通的查詢,另一個是使用 AsNoTracking 方法的非跟蹤查詢。我還將通過一個實驗來演示我是如何解決我的一個客戶遇到的性能問題。

我假設(shè)你對 EF Core 已經(jīng)有了一定的認識,但在深入學習之前,我們先來了解一下如何使用 EF Core,以確保我們已經(jīng)掌握了一些基本知識。這是一個“深入研究”的課題,所以我準備大量的技術(shù)細節(jié),希望我的描述方式你能理解。

本文是“深入理解 EF Core”系列中的第一篇。以下是本系列文章列表:

  • 當 EF Core 從數(shù)據(jù)庫讀取數(shù)據(jù)時發(fā)生了什么?(本文)

  • 當 EF Core 寫入數(shù)據(jù)到數(shù)據(jù)庫時發(fā)生了什么?(敬請期待)

概要

  • EF Core 有兩種方法從數(shù)據(jù)庫中讀取數(shù)據(jù)(也稱為查詢):普通 LINQ 查詢和包含 AsNoTracking 方法的非跟蹤 LINQ 查詢。

  • 這兩種方法查詢的返回類(被稱為實體類),它連接的其它的實體類(即所謂的導航屬性)也被同時加載,但這兩種法如何連接及連接的內(nèi)容是不一樣的。

  • 普通查詢接受的是 DbContext 執(zhí)行讀取時所有數(shù)據(jù)的副本——此時的實體類稱為被跟蹤。這允許加載的實體類參與數(shù)據(jù)庫的更新操作。

  • 普通查詢還會有一些其它的復雜底層實現(xiàn),稱為關(guān)系修補(fixup),用于描述讀入的實體類和其他被跟蹤實體之間的連接關(guān)系。

  • AsNoTracked 非跟蹤查詢沒有副本,所以它沒有被跟蹤——這意味著它比普通查詢更快。這也意味著它不會用于數(shù)據(jù)庫的寫操作。

  • 最后,我將展示 EF Core 普通查詢中一個鮮為人知的特性,以此作為示例,說明通過導航屬性連接實體類的關(guān)系是多么智能。

EF Core 如何讀取數(shù)據(jù)庫數(shù)據(jù)

提示:如果你已經(jīng)對 EF Core 有一定的認識,那么你可以跳過這一節(jié),這部分只是一個如何讀取數(shù)據(jù)庫的例子。

為了能讓你更好地理解,我先描述一個數(shù)據(jù)庫結(jié)構(gòu),然后再給出一個簡單的數(shù)據(jù)庫讀取示例。下面是一些基本表的結(jié)構(gòu)和它們之間的關(guān)系。

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

這些表被映射到具有類似名稱的類,例如 Book、BookAuthor、Author,這些類的屬性名稱與表的字段名稱相同。由于篇幅有限,我不打算展開來講這些類,但您可以在我的 GitHub 倉庫[1]中查看這些類。

EF Core 讀取數(shù)據(jù)庫需要下面五部分:

  1. 數(shù)據(jù)庫服務器,如 SQL server, Sqlite, PostgreSQL 等。

  2. 具有數(shù)據(jù)的數(shù)據(jù)庫。

  3. 映射到數(shù)據(jù)表的類(稱為實體類)。

  4. 一個繼承 DbContext 的類,該類包含 EF Core 的配置。

  5. 最后,從數(shù)據(jù)庫讀取數(shù)據(jù)的命令。

下面的單元測試代碼來自我的 GitHub 創(chuàng)庫[2],展示了一個簡單的示例,它從現(xiàn)有數(shù)據(jù)庫中讀取 4 個 Book 實體及其關(guān)聯(lián)的 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);
  }
}

現(xiàn)在,如果我們將單元測試代碼對應到上面的 5 部分,結(jié)果是這樣的:

  1. 數(shù)據(jù)庫服務器——第 5 行:我選擇了一個 Sqlite 數(shù)據(jù)庫服務器,在本例中是 SqliteInMemory.CreateOptions 方法,它使用我的一個 NuGet 包 EfCore.TestSupport 創(chuàng)建了一個內(nèi)存數(shù)據(jù)庫(內(nèi)存中的數(shù)據(jù)庫對于單元測試非常有用,因為你可以為這個測試建立一個新的空數(shù)據(jù)庫)。

  2. 具有數(shù)據(jù)的數(shù)據(jù)庫——第 6 行:我將在下一篇文章介紹數(shù)據(jù)是如何寫入數(shù)據(jù)庫的,現(xiàn)在假設(shè)有一個數(shù)據(jù)庫包含 4 本書信息,其中兩本書的作者是同一個人。

  3. 實體類——代碼里這里沒有展示,但是你可以在這里查看這些類[1]。其中有一個 Books 實體類,通過一個名為 BookAuhor 的實體類多對多關(guān)聯(lián) Authors 實體類。

  4. 一個繼承 DbContext 的類——第 7 行:EfCoreContext 類繼承了 DbContext 類并配置了從類到數(shù)據(jù)庫的映射關(guān)系(你可以在我的 GitHub 倉庫[3] 中查看該類)。

  5. 從數(shù)據(jù)庫讀取數(shù)據(jù)的命令——第 10 到 13 行,這是一個查詢:

  • 第 10 行 — context 為 EfCoreContext 的實例,通過它訪問你的數(shù)據(jù)庫,.Books 表示您希望訪問 Books 表。

  • 第 11 行 — Include 被稱為貪婪加載,它告訴 EF Core 當它加載 Books 時,也應該加載關(guān)聯(lián)到的所有 BookAuthor 實體類。

  • 第 12 行 — ThenInclude 是繼續(xù)貪婪加載,它告訴 EF Core 當它加載一個 BookAuthor 時,它也應該加載關(guān)聯(lián)到該 BookAuthor 的 Author 實體類。

所有這一切查詢出來是一個結(jié)果集,其中有普通屬性,像 Books 的 Title 屬性;有關(guān)聯(lián)實體類的導航屬性,像 Books 的 AuthorsLink 屬性。

這個示例稱為查詢或讀取,也是四種數(shù)據(jù)庫訪問類型之一,即 CRUD(新增、讀取、更新和刪除)。我將在下一篇文章中介紹新增和更新。

EF Core 如何表示讀取的數(shù)據(jù)

當你查詢數(shù)據(jù)庫時,EF Core 會將數(shù)據(jù)庫返回的數(shù)據(jù)轉(zhuǎn)換為實體類并填充導航屬性的值。在本節(jié)中,我們將研究兩種類型的查詢步驟——普通查詢(即沒有 AsNoTracking 方法,也稱為讀寫查詢)和添加了 AsNoTracking 方法的非跟蹤查詢(稱為只讀查詢)。

我們先來看一下最初 LINQ 語句是如何轉(zhuǎn)換成數(shù)據(jù)庫相應的查詢命令然后返回數(shù)據(jù)的。對于我們將要看到的兩種類型的查詢來說,這是很常見的操作。關(guān)于查詢的第一部分,請參見下圖。

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

有一些非常復雜的代碼將你的 LINQ 轉(zhuǎn)換為數(shù)據(jù)庫查詢命令,但這些內(nèi)部細節(jié)我們不必關(guān)心。如果你的 LINQ 不能被翻譯,你會從 EF Core 得到一個異常消息,其中包含類似“不能被翻譯”的描述詞語。此外,當數(shù)據(jù)返回時,像 Value Converters[4] 這樣的特性可能會調(diào)整數(shù)據(jù)。

本節(jié)展示了查詢的第一部分,其中 LINQ 被轉(zhuǎn)換為數(shù)據(jù)庫命令并返回所有正確的值?,F(xiàn)在我們來看查詢的第二部分,在這里 EF Core 獲取返回值并將它們轉(zhuǎn)換為實體類的實例,并填充導航屬性。我們將分別看看兩種類型的查詢。

1. 普通查詢(讀寫查詢)

普通查詢讀取數(shù)據(jù)的方式可以修改數(shù)據(jù)并更新到數(shù)據(jù)庫,這就是我將其稱為讀寫查詢的原因。它不會自動更新數(shù)據(jù)(請參閱下一篇文章,了解如何寫入數(shù)據(jù)庫)。如果你要更新數(shù)據(jù),你的查詢必須是讀寫查詢。

我在介紹中給出的示例執(zhí)行的是一個普通讀寫查詢,讀取帶有 AuthorsLink 實例的示例。下面是該示例的查詢部分的代碼:

var books = context.Books
  .Include(r => r.AuthorsLink)
  .ThenInclude(r => r.Author)
  .ToList();

然后 EF Core 通過三個步驟將這些值轉(zhuǎn)換并填充含有導航屬性的實體類。下圖顯示了這三個步驟以及生成的實體類及其導航屬性的實體類。

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

讓我們來分析一下這三個步驟:

  1. 創(chuàng)建類并填充數(shù)據(jù)。它接受數(shù)據(jù)庫返回的值,并填充非導航(稱為標量)屬性、字段等。在 Book 實體類中,是 BookId(主鍵)、Title 等屬性——參見上圖左下角淺藍色矩形。

  2. 修補關(guān)聯(lián)關(guān)系。首先是填入主鍵和外鍵的信息,它們定義如何相互關(guān)聯(lián)數(shù)據(jù)。然后,EF Core 使用這些鍵設(shè)置實體類之間的導航屬性(如圖中藍色粗線所示)。這個關(guān)系的修補所需的信息不僅是查詢讀入的實體類,它還會查看 DbContext 中跟蹤的每個實體,并填充導航屬性。這是一個強大的功能,但你的被跟蹤實體越多,所需消耗時間也越多——這就是為什么需要 AsNoTracking 來實現(xiàn)更快的查詢。

  3. 創(chuàng)建跟蹤快照。跟蹤快照是返回給用戶的實體類的一個副本,加上它所隱藏的與每個實體類的關(guān)聯(lián)關(guān)系——若一個實體處于被跟蹤狀態(tài),這意味著它將會發(fā)生修改并會寫入到數(shù)據(jù)庫中。

2. 非跟蹤查詢(只讀查詢)

非跟蹤查詢,即使用 AsNoTracking 方法的查詢,是一個只讀查詢。這意味著,當 SaveChanges 方法被調(diào)用時,你讀取的任何內(nèi)容都不會被寫入數(shù)據(jù)庫。非跟蹤查詢的查詢效率更高,在下一節(jié)中,我將介紹非跟蹤查詢以及與普通查詢的其他區(qū)別。

在前文的示例之后,我修改了查詢代碼,添加了下面的 AsNoTracking 方法(請看第 2 行):

var books = context.Books
  .AsNoTracking()
  .Include(r => r.AuthorsLink)
  .ThenInclude(r => r.Author)
  .ToList();

這里的 LINQ 查詢只有上面的普通查詢的前兩個步驟(沒有第三個步驟)。下圖顯示了 AsNoTracking 查詢的步驟。

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

步驟如下:

  1. 創(chuàng)建類并填充數(shù)據(jù)。它接受數(shù)據(jù)庫返回的值,并填充非導航(稱為標量)屬性、字段等。在 Book 實體類中,是 BookId(主鍵)、Title 等屬性——參見上圖左下角淺藍色矩形。

  2. 修補關(guān)聯(lián)關(guān)系。首先是填入主鍵和外鍵的信息,它們定義如何相互關(guān)聯(lián)數(shù)據(jù)。然后,EF Core 使用這些鍵設(shè)置實體類之間的導航屬性(如圖中藍色粗線所示)。這個關(guān)系的修補所需的信息不僅是查詢讀入的實體類,它還會查看 DbContext 中跟蹤的每個實體,并填充導航屬性。這是一個強大的功能,但你的被跟蹤實體越多,所需消耗時間也越多——這就是為什么需要 AsNoTracking 來實現(xiàn)更快的查詢。

普通查詢和非跟蹤查詢的區(qū)別

現(xiàn)在讓我們比較這兩種查詢比較明顯的區(qū)別。

  1. 非跟蹤查詢查詢的性能更好。使用非跟蹤查詢查詢的主要原因是性能。非跟蹤查詢查詢表現(xiàn)為:

  • 稍微快一點,使用的內(nèi)存稍微少一點,因為它不需要創(chuàng)建跟蹤快照。

  • 避免沒有必要的跟蹤快照可以提高 SaveChanges 的性能,因為它不必檢查跟蹤快照以查找更改。

  • 稍微快一點,因為修補關(guān)聯(lián)關(guān)系時沒有所謂的身份解析。這就是為什么你會得到兩個具有相同數(shù)據(jù)的 Author 實例。

  1. 非跟蹤查詢修補關(guān)聯(lián)關(guān)系時只鏈接查詢中的實體。在普通查詢中,我已經(jīng)說過修補關(guān)聯(lián)關(guān)系時連接的是查詢中的實體和當前跟蹤的實體,但是非跟蹤查詢只修補查詢中的實體關(guān)系。

  2. 非跟蹤查詢并不總是代表數(shù)據(jù)庫關(guān)系。這兩種類型查詢之間的關(guān)系修補的另一個區(qū)別是,非跟蹤查詢關(guān)系修補更快,它不需要標識的解析。這可以為數(shù)據(jù)庫中的同一行生成多個實例——見上圖右下角藍色的 Author 實體和注釋。如果只是向用戶顯示數(shù)據(jù),那么這種差異并不重要,但是如果具有業(yè)務邏輯,那么多個實例不能正確反映數(shù)據(jù)的結(jié)構(gòu),就可能會有問題。

對層級數(shù)據(jù)有用的關(guān)系修補特性

關(guān)聯(lián)關(guān)系修補的步驟是非常智能的,特別是在普通查詢中。下面我想向你展示我是如何利用關(guān)系修補的特性來解決一個客戶項目中的性能問題的。

我曾在一家公司工作,那里的許多數(shù)據(jù)處理都是層次化結(jié)構(gòu)的,即數(shù)據(jù)具有一系列深度不確定的關(guān)聯(lián)關(guān)系。問題是我必須先解析整個層次結(jié)構(gòu),然后才能呈現(xiàn)這些數(shù)據(jù)。我最初是通過貪婪的方式加載前兩個層級,然后顯式地加載更深的層級來實現(xiàn)這一點的。它可以工作,但是性能非常慢,并且數(shù)據(jù)庫因大量單數(shù)據(jù)庫訪問而超載。

這不得不讓我思考解決辦法,如果普通查詢的關(guān)系修補那么智能的話,它能幫助我提高查詢的性能嗎?它可以!讓我給你舉一個公司員工的例子。下圖顯示了我們想要加載的公司的層次結(jié)構(gòu)。

.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的

你可以接龍式地使用 .Include(x => x.WorksForMe).ThenInclude(x => x.WorksForMe)… 等等來加載所需的層級信息,但結(jié)果是一個 .Include(x => x.WorksForMe) 就夠了。因為 EF Core 的關(guān)系修補為你做了剩下的事情,這一點很驚奇,但也很有用。

例如,如果我想查詢角色為 Development 的所有員工(每個員工都有一個名為 WhatTheyDo 的屬性和名為 Role 的屬性,該 Role 包含他們工作的部門),我可以這樣編寫代碼:

var devDept = context.Employees
  .Include(x => x.WorksFromMe)
  .Where(x => x.WhatTheyDo.HasFlag(Roles.Development))
  .ToList();

 這將創(chuàng)建一個查詢,用于加載角色為 Development 的所有員工,并且在員工實體類上修補與 WorksFoMe 導航屬性(集合)和 Manager 導航屬性(單個)的關(guān)系。通過只執(zhí)行一個查詢,既提高了查詢花費的時間,又減少了數(shù)據(jù)庫服務器上的負載。

總結(jié)

你已經(jīng)看到了兩種類型的查詢,我稱之為 a)普通的讀寫查詢,和 b) 非跟蹤的只讀查詢。對于每一種查詢類型,我都向你展示了 EF Core “幕后”是如何讀取數(shù)據(jù)并展示的。他們工作方式的不同也表現(xiàn)出他們的優(yōu)勢和劣勢。

非跟蹤查詢是只讀查詢的解決方案,因為它比普通讀寫查詢更快。但是您應該記住關(guān)系修補的機制,它可以在數(shù)據(jù)庫只有一個關(guān)系的情況下創(chuàng)建類的多個實例。

普通的讀寫查詢是查詢跟蹤實體的解決方案,這意味著你可以在創(chuàng)建、更新和刪除數(shù)據(jù)時使用它們。普通的讀寫查詢確實會占用更多的時間和內(nèi)存資源,但是有一些有用的特性,比如自動鏈接到其他被跟蹤的實體類實例。

到此,相信大家對“.net EF Core專題:EF Core 讀取數(shù)據(jù)是如何運行的”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI