熟悉EFCore的加載方式,提高程序性能
在EFCore 中,數(shù)據(jù)加載策略對(duì)于提高應(yīng)用程序的性能至關(guān)重要,數(shù)據(jù)加載是指從數(shù)據(jù)庫(kù)中檢索實(shí)體及其相關(guān)的導(dǎo)航屬性。EF Core 提供了以下幾種數(shù)據(jù)加載方式,本篇文章將詳細(xì)介紹 EF Core中的數(shù)據(jù)加載。
1.延遲加載(Lazy Loading)
官網(wǎng)說(shuō)明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/lazy
延遲加載是 EF Core 默認(rèn)的數(shù)據(jù)加載方式。當(dāng)訪問(wèn)一個(gè)實(shí)體中的導(dǎo)航屬性時(shí),EF Core 會(huì)自動(dòng)從數(shù)據(jù)庫(kù)中加載相關(guān)的實(shí)體。例如,我們可以通過(guò)以下代碼獲取一個(gè)訂單的所有訂單項(xiàng):
var order = dbContext.Orders.Find(orderId);
var orderItems = order.OrderItems; // 自動(dòng)加載所有訂單項(xiàng)
在上面的代碼中,我們首先從數(shù)據(jù)庫(kù)中獲取了一個(gè)訂單實(shí)體,然后通過(guò)訂單實(shí)體的導(dǎo)航屬性 OrderItems 獲取了該訂單的所有訂單項(xiàng)。EF Core 會(huì)自動(dòng)發(fā)出另一次查詢語(yǔ)句,從數(shù)據(jù)庫(kù)中獲取所有訂單項(xiàng)。這種加載方式稱為延遲加載,因?yàn)?EF Core 延遲加載了訂單項(xiàng),直到我們?cè)L問(wèn)了 OrderItems 屬性時(shí)才加載。
延遲加載的優(yōu)點(diǎn)是方便快捷,不需要手動(dòng)編寫(xiě)額外的代碼來(lái)加載關(guān)聯(lián)實(shí)體。但是,也存在一些缺點(diǎn)。由于 EF Core 需要發(fā)出多條查詢語(yǔ)句,延遲加載可能會(huì)導(dǎo)致性能問(wèn)題。此外,如果在離開(kāi) DbContext 的范圍之后訪問(wèn)導(dǎo)航屬性,將會(huì)引發(fā)異常。
2.顯式加載(Explicit Loading)
官網(wǎng)說(shuō)明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/explicit
顯式加載是另外一種數(shù)據(jù)加載方式,它要求我們手動(dòng)加載關(guān)聯(lián)實(shí)體。顯式加載通常用于需要精細(xì)控制數(shù)據(jù)加載和提高性能的場(chǎng)景。
在 EF Core 中,可以使用 Load 方法進(jìn)行顯式加載。例如,我們可以通過(guò)以下代碼手動(dòng)加載一個(gè)訂單的所有訂單項(xiàng):
var order = dbContext.Orders.Find(orderId);
dbContext.Entry(order).Collection(o => o.OrderItems).Load(); // 手動(dòng)加載所有訂單項(xiàng)
在上面的代碼中,我們首先從數(shù)據(jù)庫(kù)中獲取了一個(gè)訂單實(shí)體,然后使用 Entry 方法獲取與該實(shí)體相關(guān)聯(lián)的 DbContext 中的實(shí)體條目,最后調(diào)用 Collection 方法指定要加載的導(dǎo)航屬性,并使用 Load 方法手動(dòng)加載所有訂單項(xiàng)。這種加載方式稱為顯式加載,因?yàn)槲覀冃枰@式地編寫(xiě)代碼來(lái)加載關(guān)聯(lián)實(shí)體。
顯式加載的優(yōu)點(diǎn)是可以減少不必要的查詢,提高性能。此外,它還允許我們精細(xì)控制數(shù)據(jù)加載,避免在加載大量數(shù)據(jù)時(shí)出現(xiàn)性能問(wèn)題。但是,它需要我們手動(dòng)編寫(xiě)額外的代碼來(lái)加載關(guān)聯(lián)實(shí)體。
3.預(yù)先加載 (Eager Loading)
官網(wǎng)說(shuō)明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/eager
在查詢時(shí),通過(guò)使用 Include 方法指定要加載的導(dǎo)航屬性,從而一次性加載所有相關(guān)的實(shí)體。這種加載方式稱為預(yù)先加載(Eager Loading)。預(yù)先加載允許我們?cè)趩蝹€(gè)查詢中檢索所有實(shí)體和導(dǎo)航屬性,避免了通過(guò)多次查詢檢索數(shù)據(jù)的開(kāi)銷,提高了性能。
當(dāng)使用 EF Core 進(jìn)行預(yù)先加載(Eager Loading)時(shí),可以通過(guò)使用 Include 方法來(lái)指定要加載的導(dǎo)航屬性。
假設(shè)我們有兩個(gè)實(shí)體類 Order 和 OrderItem,它們之間存在一對(duì)多的關(guān)系,一個(gè)訂單可以包含多個(gè)訂單項(xiàng)。下面是一個(gè)示例代碼,展示如何使用預(yù)先加載來(lái)一次性加載訂單及其所有訂單項(xiàng):
var order = dbContext.Orders
.Include(o => o.OrderItems) // 使用 Include 方法指定要加載的導(dǎo)航屬性
.FirstOrDefault(o => o.Id == orderId);
在上面的代碼中,我們首先通過(guò) dbContext.Orders 獲取 Orders 實(shí)體集合,并使用 Include 方法指定要加載的導(dǎo)航屬性 OrderItems。然后,我們使用 FirstOrDefault 方法根據(jù)訂單的 Id 獲取第一個(gè)匹配的訂單實(shí)體。EF Core 將會(huì)在查詢時(shí)將所有相關(guān)的訂單項(xiàng)也加載到內(nèi)存中。
通過(guò)預(yù)先加載,我們可以在單個(gè)查詢中獲取訂單及其所有訂單項(xiàng)的數(shù)據(jù),避免了多次查詢的開(kāi)銷,提高了性能。這在需要同時(shí)訪問(wèn)和操作訂單及其訂單項(xiàng)數(shù)據(jù)時(shí)非常有用。
需要注意的是,預(yù)先加載可能導(dǎo)致加載過(guò)多的數(shù)據(jù),造成性能問(wèn)題。因此,我們應(yīng)該謹(jǐn)慎使用預(yù)先加載,并根據(jù)具體情況進(jìn)行權(quán)衡和優(yōu)化。
4.顯式轉(zhuǎn)換(Explicit Conversion)
在查詢時(shí),可以使用 Cast 和 OfType 方法將查詢結(jié)果強(qiáng)制轉(zhuǎn)換為指定類型的實(shí)體。這種加載方式稱為顯式轉(zhuǎn)換。
假設(shè)我們有兩個(gè)實(shí)體類 Order 和 OrderItem,它們之間存在一對(duì)多的關(guān)系,一個(gè)訂單可以包含多個(gè)訂單項(xiàng)。下面是一個(gè)示例代碼,展示如何使用顯式轉(zhuǎn)換來(lái)將查詢結(jié)果轉(zhuǎn)換為 Order 和 OrderItem 實(shí)體:
var orders = dbContext.Orders
.Include(o => o.OrderItems) // 使用 Include 方法指定要加載的導(dǎo)航屬性
.ToList();
var orderItems = orders.SelectMany(o => o.OrderItems).ToList(); // 通過(guò) SelectMany 方法獲取所有訂單項(xiàng)
var convertedOrders = orders.Cast<Order>().ToList(); // 將查詢結(jié)果轉(zhuǎn)換為 Order 類型
var convertedOrderItems = orderItems.Cast<OrderItem>().ToList(); // 將查詢結(jié)果轉(zhuǎn)換為 OrderItem 類型
在上面的代碼中,我們首先通過(guò) dbContext.Orders 獲取 Orders 實(shí)體集合,并使用 Include 方法指定要加載的導(dǎo)航屬性 OrderItems。然后,我們使用 ToList 方法將查詢結(jié)果轉(zhuǎn)換為列表,并使用 SelectMany 方法獲取所有訂單項(xiàng)。最后,我們使用 Cast 方法將查詢結(jié)果分別轉(zhuǎn)換為 Order 和 OrderItem 類型,并使用 ToList 方法將結(jié)果轉(zhuǎn)換為列表。
通過(guò)顯式轉(zhuǎn)換,我們可以將查詢結(jié)果轉(zhuǎn)換為指定類型的實(shí)體,以便在進(jìn)行進(jìn)一步的操作和處理時(shí),更好地進(jìn)行類型匹配和類型轉(zhuǎn)換。需要注意的是,在進(jìn)行顯式轉(zhuǎn)換之前,我們應(yīng)該確保查詢結(jié)果中的實(shí)體類型與目標(biāo)類型兼容。
5.原始 SQL 查詢(Raw SQL Queries)
官網(wǎng)說(shuō)明:https://learn.microsoft.com/zh-cn/ef/core/querying/sql-queries
除了使用 LINQ 查詢語(yǔ)法外,EF Core 還支持執(zhí)行原始 SQL 查詢。原始 SQL 查詢可以用于執(zhí)行復(fù)雜的查詢或利用數(shù)據(jù)庫(kù)特定的功能。通過(guò)原始 SQL 查詢,我們可以完全控制查詢語(yǔ)句和結(jié)果集。
在訂單和訂單明細(xì)的例子中,我們可以使用原始 SQL 查詢來(lái)執(zhí)行自定義的 SQL 查詢,并將查詢結(jié)果轉(zhuǎn)換為實(shí)體。
假設(shè)我們有兩個(gè)實(shí)體類 Order 和 OrderItem,它們之間存在一對(duì)多的關(guān)系,一個(gè)訂單可以包含多個(gè)訂單項(xiàng)。下面是一個(gè)示例代碼,展示如何使用原始 SQL 查詢來(lái)獲取 Order 和 OrderItem 實(shí)體:
var orders = dbContext.Orders.FromSqlRaw("SELECT * FROM Orders").ToList(); // 使用 FromSqlRaw 方法執(zhí)行自定義的 SQL 查詢
var orderIds = string.Join(",", orders.Select(o => o.Id)); // 構(gòu)造訂單 ID 列表字符串
var orderItems = dbContext.OrderItems.FromSqlRaw($"SELECT * FROM OrderItems WHERE OrderId IN ({orderIds})").ToList(); // 使用 FromSqlRaw 方法執(zhí)行帶參數(shù)的 SQL 查詢
foreach (var order in orders)
{
order.OrderItems = orderItems.Where(oi => oi.OrderId == order.Id).ToList(); // 為每個(gè)訂單設(shè)置訂單項(xiàng)
}
在上面的代碼中,我們首先使用 FromSqlRaw 方法執(zhí)行自定義的 SQL 查詢,獲取所有的 Order 實(shí)體。然后,我們使用 LINQ 表達(dá)式構(gòu)造訂單 ID 列表字符串,以便在后續(xù)查詢中使用。接著,我們?cè)俅问褂?FromSqlRaw 方法執(zhí)行帶參數(shù)的 SQL 查詢,獲取所有的 OrderItem 實(shí)體。最后,我們使用 Where 方法為每個(gè)訂單設(shè)置對(duì)應(yīng)的訂單項(xiàng)。
通過(guò)原始 SQL 查詢,我們可以執(zhí)行自定義的 SQL 查詢,并獲取指定的實(shí)體數(shù)據(jù)。需要注意的是,在構(gòu)造 SQL 查詢時(shí),我們需要避免 SQL 注入攻擊,并確保查詢結(jié)果與實(shí)體類的屬性匹配。此外,原始 SQL 查詢可能會(huì)影響性能和可維護(hù)性,因此在實(shí)際使用中,應(yīng)該謹(jǐn)慎使用。
綜合對(duì)比
下表是 EF Core 提供的幾種數(shù)據(jù)加載方式的綜合對(duì)比:
數(shù)據(jù)加載方式 | 描述 | 優(yōu)點(diǎn) | 缺點(diǎn) |
延遲加載(Lazy Loading) | 默認(rèn)情況下,訪問(wèn)導(dǎo)航屬性時(shí)自動(dòng)執(zhí)行查詢加載關(guān)聯(lián)數(shù)據(jù)。 | 簡(jiǎn)單易用,避免無(wú)謂的數(shù)據(jù)查詢。 | 引發(fā) N+1 查詢問(wèn)題,性能較差。 |
顯式加載(Explicit Loading) | 使用 Entry 對(duì)象的 Collection 或 Reference 方法手動(dòng)觸發(fā)加載導(dǎo)航屬性數(shù)據(jù)。 | 靈活控制加載行為,避免不必要的數(shù)據(jù)查詢。 | 需要手動(dòng)執(zhí)行加載操作,較為繁瑣。 |
急切加載(Eager Loading) | 使用 Include 方法指定要預(yù)先加載的導(dǎo)航屬性,查詢時(shí)一并加載相關(guān)實(shí)體數(shù)據(jù)。 | 提高查詢性能,避免 N+1 查詢問(wèn)題。 | 查詢結(jié)果包含大量無(wú)用數(shù)據(jù),增加數(shù)據(jù)傳輸和內(nèi)存消耗。 |
顯式轉(zhuǎn)換(Explicit Conversion) | 將原始 SQL 查詢結(jié)果手動(dòng)映射為實(shí)體對(duì)象。 | 靈活處理復(fù)雜查詢需求,避免使用 LINQ 表達(dá)式。 | 安全性和可維護(hù)性較差,需要手動(dòng)構(gòu)造 SQL 查詢語(yǔ)句。 |
原始 SQL 查詢(Raw SQL Queries) | 執(zhí)行自定義的原始 SQL 查詢,并將查詢結(jié)果轉(zhuǎn)換為實(shí)體。 | 處理復(fù)雜的查詢需求,靈活性高。 | 安全性和可維護(hù)性較差,需要手動(dòng)構(gòu)造 SQL 查詢語(yǔ)句。 |
以上是對(duì) EF Core 提供的數(shù)據(jù)加載方式的綜合對(duì)比,根據(jù)具體的需求和場(chǎng)景,應(yīng)選擇適合的加載方式。延遲加載簡(jiǎn)單易用但性能較差,顯式加載需要手動(dòng)觸發(fā)加載操作,急切加載可以提高查詢性能但增加了數(shù)據(jù)傳輸和內(nèi)存消耗,顯式轉(zhuǎn)換適用于復(fù)雜查詢需求,原始 SQL 查詢具有靈活性但安全性和可維護(hù)性較差。這些加載方式提供了不同的靈活性和性能特征,可以根據(jù)具體需求選擇適合的加載方式,并從中獲得最佳的性能和效果。
參考資料:EF Core 文檔:https://docs.microsoft.com/ef/core/。