這是 EF Core 系列的第四篇文章,上一篇文章講述了 EF Core 中的實體遷移與數據播種。
這篇文章盤點一下 EF Core 的幾種數據查詢方式,內容較多分上下兩篇。
點擊上方或后方藍字,閱讀 EF Core 系列合集。
簡單查詢
在 EF Core 中,每個查詢都由三個主要部分組成:
通過?
ApplicationContext
?的?DbSet
?屬性連接到數據庫使用一系列的 LINQ 或 EF Core 命令
執行查詢
這是一個最簡單的示例:
public?void?Run()
{var?accounts?=?_context.Accounts.Where(s?=>?s.Age?>?16).ToList();
}
從這個查詢中,我們可以看到查詢的三個主要部分:
「_context.Accounts」?是查詢第一部分,通過?DbSet<Account>
?屬性,訪問數據庫中的?Account
?表。
「Where(s => s.Age > 25)」?是查詢的第二部分,使用 LINQ 方法篩選需要的行。
最后,「ToList()」?方法用來來執行這個查詢。
需要注意的是,當我們在 EF Core 中編寫只讀查詢時,可以添加?AsNoTracking
?方法提高查詢效率:
_context.Accounts.AsNoTracking()
使用 AsNoTracking 方法時,EF Core 不會跟蹤加載實體的變化。
關系型查詢
在 EF Core 查詢導航屬性(表關聯字段)的方式有多種:「貪婪加載」、「顯式加載」和「懶惰加載」。
貪婪加載
貪婪加載也叫預先加載。
所謂貪婪加載,就是在查詢結果中包含導航關系,而這就需要明確的要求。
比如這個示例中,Account 擁有兩個導航屬性:
AccountDetails
屬性是一對一的導航關系;
AccountSubjects
屬性是一對多的導航關系。
運行這個簡單查詢的結果如下:
可以發現,控制臺的結果中,兩個導航屬性的值都是?Null
。
在 EF Core 中,只有明確要求的情況下,才會在結果中包含導航關系。這個簡單查詢中,沒有明確要求包含導航關系。
如果使用貪婪加載,可以讓 EF Core 在查詢結果中包含導航屬性的值。
貪婪加載通過使用?Include()
?和?ThenInclude()
?方法實現,如下所示:
var?accounts?=?_context.Accounts.Include(e?=>?e.AccountSubjects).Where(s?=>?s.Age?>?16).ToList();
Include 方法用來加載第一層導航關系,如果想進一步加載導航關系呢?
比如?AccountSubjects
?屬性中有兩個一對一導航,分別是?Accout
?屬性和?Subject
?屬性:
如果我們想通過?AccountSubjects
?導航屬性,進一步查詢出?Subject
?屬性,就可以這么做 :
var?accounts?=?_context.Accounts.Include(e?=>?e.AccountSubjects).ThenInclude(s?=>?s.Subject).Where(s?=>?s.Age?>?16).ToList();
ThenInclude
?方法用來進一步加載導航關系。該方法可以無限遞進非關系深度,如果關系不存在,查詢也不會失敗,只是不會返回任何東西。
「貪婪加載的優點是,以一種高效的方式,查詢了關系型數據,使用了最少的數據庫訪問次數;」
「它的缺點是,一次性加載了所有的數據,即使我們不需要其中的某些數據。」
顯式加載
所謂顯式加載,就是 EF Core 顯式地將關系,加載到已經加載的實體中。
比如這個示例:
var?account?=?_context.Accounts.FirstOrDefault();_context.Entry(account).Collection(ss?=>?ss.AccountSubjects).Load();foreach?(var?accountSubject?in?account.AccountSubjects)
{_context.Entry(accountSubject).Reference(s?=>?s.Subject).Load();
}
我們首先加載的是?Acount
?實體,然后通過?AccountSubjects
?導航屬性關聯所有相關的子項。
在這種情況下,Acount
?實體被稱為主實體。
Collection
?方法可以把一個集合納入主實體,Reference
?方法可以把單一的實體納入主實體。
Account
?實體通過使用?Collection
?方法,包含了?AccountSubjec
?集合。
AccountSubject
?實體通過使用?Reference
?方法,包含了?Subject
?實體。
使用顯式加載時,除了?Load
?加載方法,還可以使用查詢方法,它允許將查詢應用到關系中:
var?count?=?_context.Entry(account).Collection(a?=>?a.AccountSubjects).Query().Count();var?subjects?=?_context.Entry(account).Collection(a?=>?a.AccountSubjects).Query().Select(s?=>?s.Subject).ToList();
「顯式加載的好處是,只有當真正需要的時候,我們才會在實體類上加載一個導航關系。」
另一個好處是,如果我們有復雜的業務邏輯,那就可以分別加載導航關系。
另外,導航關系加載可以封裝到一個方法、甚至是一個類中,從而使代碼更容易閱讀和維護。
不過,這種方法的缺點是,會產生更多的數據庫查詢次數,來加載所有需要的關系,會降低查詢的效率。
懶惰加載
懶加載也叫延遲加載、按需加載,它和貪婪加載相反,顧名思義,暫時不需要的數據就不加載,而是推遲到使用它時再加載。
延遲加載是一個比較重要的數據訪問特性,它可以有效地減少與數據源的交互。
注意,這里所指的交互不是指交互次數,而是指交互的數據量。
EF Core 中默認是不開啟這個功能的,因為在使用不當的情況下,它會降低應用的性能。
想要使用懶加載,最簡單的辦法就是安裝?Microsoft.EntityFrameworkCore.Proxies
?庫,使用代理模式實現懶加載。
在上下文類的配置方法中啟用懶加載代理:
protected?override?void?OnConfiguring(DbContextOptionsBuilder?optionsBuilder)
{optionsBuilder.UseLazyLoadingProxies();
}
配置完成后,EF Core 會為任何可以被重載的導航屬性,啟用懶惰加載。
需要注意的是,這是一種全局配置,所有的導航屬性都必須使用?virtual
?修飾,否則會發生異常錯誤。
不過,這樣一來的話,所有的導航屬性都默認啟用了懶加載。
除了使用代理模式,還可以使用 EF Core 中的懶加載服務,這種方式不需要用?virtual
?修飾導航屬性,而且可以只針對特定實體進行懶加載。
具體來看示例:
public?class?Account
{private?readonly?ILazyLoader?_lazyLoader;public?Account(ILazyLoader?lazyLoader){_lazyLoader?=?lazyLoader;}private?ICollection<AccountSubject>?_accountSubjects;public?ICollection<AccountSubject>?AccountSubjects{get?=>?_lazyLoader?.Load(this,?ref?_accountSubjects);set?=>?_accountSubjects?=?value;}}
使用構造函數注入的方式,將?ILazyLoader
?服務注入到實體類中,然后修改需要開啟懶加載的字段。
需要注意的是,濫用懶加載,會造成性能上的問題。
雖然懶加載只在需要讀取關聯數據的時候才進行加載,但是如果在遍歷中使用的話,每次讀取一條數據,那么就會查詢一次數據庫,增加了訪問數據庫的次數,會導致數據庫的壓力增大。
貪婪加載也一樣會有性能上的問題,因為一次性讀取所有相關的數據,有可能會導致部分數據在實際上用不到,從而使查詢數據的效率降低。
所以,我們應該清楚什么時候應該使用哪種加載方式:
如果在開發時不確定是否會需要相關聯的數據,那么可以選擇懶加載,待確定需要后再加載它。
如果在開發時就可以預見,需要一次性加載所有的數據,而且需要包含導航關系的所有數據, 那么使用貪婪加載是比較好的選擇。
更多精彩內容,請關注我▼▼
如果喜歡我的文章,那么
在看和轉發是對我最大的支持!
(戳下面藍字閱讀)
ASP.NET 6 中間件系列
查缺補漏系統學習 EF Core 6? 系列
推薦關注微信公眾號:碼俠江湖
? ? ? ? ? ? ? ? ? ? ? ??覺得不錯,點個在看再走喲