閱讀本文大概須要 11 分鐘。數據庫
原文:https://bit.ly/2UMiDLb
做者:Jon P Smith
翻譯:王亮
聲明:我翻譯技術文章不是逐句翻譯的,而是根據我本身的理解來表述的。其中可能會去除一些本人實在不知道如何組織但又不影響理解的句子。編程
本文將爲你詳細描繪 EF Core 從數據庫中讀取數據的「幕後」視圖。我將揭開兩種數據庫讀取方式的面紗:一個是普通的查詢,另外一個是使用 AsNoTracking 方法的非跟蹤查詢。我還將經過一個實驗來演示我是如何解決個人一個客戶遇到的性能問題。服務器
我假設你對 EF Core 已經有了必定的認識,但在深刻學習以前,咱們先來了解一下如何使用 EF Core,以確保咱們已經掌握了一些基本知識。這是一個「深刻研究」的課題,因此我準備大量的技術細節,但願個人描述方式你能理解。性能
本文是「深刻理解 EF Core」系列中的第一篇。如下是本系列文章列表:單元測試
提示:若是你已經對 EF Core 有必定的認識,那麼你能夠跳過這一節,這部分只是一個如何讀取數據庫的例子。學習
爲了能讓你更好地理解,我先描述一個數據庫結構,而後再給出一個簡單的數據庫讀取示例。下面是一些基本表的結構和它們之間的關係。測試
這些表被映射到具備相似名稱的類,例如 Book、BookAuthor、Author,這些類的屬性名稱與表的字段名稱相同。因爲篇幅有限,我不打算展開來說這些類,但您能夠在個人 GitHub 倉庫[1]中查看這些類。spa
EF Core 讀取數據庫須要下面五部分:翻譯
下面的單元測試代碼來自個人 GitHub 創庫[2],展現了一個簡單的示例,它從現有數據庫中讀取 4 個 Book 實體及其關聯的 BookAuthor 和 Authors 實體。3d
[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 部分,結果是這樣的:
SqliteInMemory.CreateOptions
方法,它使用個人一個 NuGet 包 EfCore.TestSupport 建立了一個內存數據庫(內存中的數據庫對於單元測試很是有用,由於你能夠爲這個測試創建一個新的空數據庫)。.Books
表示您但願訪問 Books 表。全部這一切查詢出來是一個結果集,其中有普通屬性,像 Books 的 Title 屬性;有關聯實體類的導航屬性,像 Books 的 AuthorsLink 屬性。
這個示例稱爲查詢或讀取,也是四種數據庫訪問類型之一,即 CRUD(新增、讀取、更新和刪除)。我將在下一篇文章中介紹新增和更新。
當你查詢數據庫時,EF Core 會將數據庫返回的數據轉換爲實體類並填充導航屬性的值。在本節中,咱們將研究兩種類型的查詢步驟——普通查詢(即沒有 AsNoTracking 方法,也稱爲讀寫查詢)和添加了 AsNoTracking 方法的非跟蹤查詢(稱爲只讀查詢)。
咱們先來看一下最初 LINQ 語句是如何轉換成數據庫相應的查詢命令而後返回數據的。對於咱們將要看到的兩種類型的查詢來講,這是很常見的操做。關於查詢的第一部分,請參見下圖。
有一些很是複雜的代碼將你的 LINQ 轉換爲數據庫查詢命令,但這些內部細節咱們沒必要關心。若是你的 LINQ 不能被翻譯,你會從 EF Core 獲得一個異常消息,其中包含相似「不能被翻譯」的描述詞語。此外,當數據返回時,像 Value Converters[4] 這樣的特性可能會調整數據。
本節展現了查詢的第一部分,其中 LINQ 被轉換爲數據庫命令並返回全部正確的值。如今咱們來看查詢的第二部分,在這裏 EF Core 獲取返回值並將它們轉換爲實體類的實例,並填充導航屬性。咱們將分別看看兩種類型的查詢。
普通查詢讀取數據的方式能夠修改數據並更新到數據庫,這就是我將其稱爲讀寫查詢的緣由。它不會自動更新數據(請參閱下一篇文章,瞭解如何寫入數據庫)。若是你要更新數據,你的查詢必須是讀寫查詢。
我在介紹中給出的示例執行的是一個普通讀寫查詢,讀取帶有 AuthorsLink 實例的示例。下面是該示例的查詢部分的代碼:
var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
而後 EF Core 經過三個步驟將這些值轉換並填充含有導航屬性的實體類。下圖顯示了這三個步驟以及生成的實體類及其導航屬性的實體類。
讓咱們來分析一下這三個步驟:
非跟蹤查詢,即便用 AsNoTracking 方法的查詢,是一個只讀查詢。這意味着,當 SaveChanges 方法被調用時,你讀取的任何內容都不會被寫入數據庫。非跟蹤查詢的查詢效率更高,在下一節中,我將介紹非跟蹤查詢以及與普通查詢的其餘區別。
在前文的示例以後,我修改了查詢代碼,添加了下面的 AsNoTracking 方法(請看第 2 行):
var books = context.Books .AsNoTracking() .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
這裏的 LINQ 查詢只有上面的普通查詢的前兩個步驟(沒有第三個步驟)。下圖顯示了 AsNoTracking 查詢的步驟。
步驟以下:
如今讓咱們比較這兩種查詢比較明顯的區別。
非跟蹤查詢查詢的性能更好。使用非跟蹤查詢查詢的主要緣由是性能。非跟蹤查詢查詢表現爲:
非跟蹤查詢修補關聯關係時只連接查詢中的實體。在普通查詢中,我已經說過修補關聯關係時鏈接的是查詢中的實體和當前跟蹤的實體,可是非跟蹤查詢只修補查詢中的實體關係。
非跟蹤查詢並不老是表明數據庫關係。這兩種類型查詢之間的關係修補的另外一個區別是,非跟蹤查詢關係修補更快,它不須要標識的解析。這能夠爲數據庫中的同一行生成多個實例——見上圖右下角藍色的 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 「幕後」是如何讀取數據並展現的。他們工做方式的不一樣也表現出他們的優點和劣勢。
非跟蹤查詢是隻讀查詢的解決方案,由於它比普通讀寫查詢更快。可是您應該記住關係修補的機制,它能夠在數據庫只有一個關係的狀況下建立類的多個實例。
普通的讀寫查詢是查詢跟蹤實體的解決方案,這意味着你能夠在建立、更新和刪除數據時使用它們。普通的讀寫查詢確實會佔用更多的時間和內存資源,可是有一些有用的特性,好比自動連接到其餘被跟蹤的實體類實例。
我但願這篇文章對您有用。祝你編程快樂!
[1]. https://bit.ly/2MXK3ZY
[2]. https://bit.ly/2Yza7QQ
[3]. https://bit.ly/2Y0UORO
[4]. https://bit.ly/2YEyg8j