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