asp.net core系列 32 EF查詢資料 必備知識(1)
一.查詢的工作原理
Entity Framework Core 使用語言整合查詢 (LINQ) 來查詢資料庫中的資料。 通過 LINQ 可使用 C#(或你選擇的其他 .NET 語言)基於派生上下文和實體類編寫強型別查詢。 LINQ 查詢的表示形式會傳遞給資料庫提供程式,進而轉換為特定的資料庫查詢語言(例如,適用於關係資料庫的 SQL)。
1.1 查詢的生命週期, 下面是每個查詢所經歷的過程概述:
(1) LINQ 查詢由 E F處理,用於生成已準備好的表示形式,由資料庫提供程式處理。快取結果,以便每次執行查詢時都不需要執行此處理。
(2) 結果將傳遞給資料庫提供程式
a.資料庫提供程式會識別出查詢的哪些部分可以在資料庫中求值。
b. 查詢的這些部分會轉換為特定資料庫的查詢語言(例如,關係資料庫的 SQL)
c. 將一個或多個查詢傳送到資料庫並返回結果集(結果是來自資料庫的值,而不是實體例項)
(3) 返回結果集處理
a.如果這是跟蹤查詢,EF會檢查資料是否代表一個實體,已存在於上下文例項的更改跟蹤器中。
如果是,則會返回現有實體
如果不是,則會建立新實體、設定更改跟蹤並返回該新實體
b.如果這是非跟蹤查詢,EF 會檢查資料是否表示此查詢結果集中的現有實體
如果是,則會返回現有實體
如果不是,則會建立新實體並返回該新實體
1.2 執行查詢時:
呼叫 LINQ運算子時,只會構建查詢在記憶體中的表示形式。 只有在使用結果時,查詢才會傳送到資料庫。觸發查詢傳送到資料庫的最常見操作如下:
(1) 在 for 迴圈中迴圈訪問結果
var blogs = from b in BloggingContext.Blogs select {....} //觸發資料庫查詢 foreach (var item in blogs) { int maxID = item.ID; }
(2) 使用 ToList、ToArray、Single、Count 等操作都會觸發資料庫查詢
BloggingContext.Blogs.ToList(); BloggingContext.Blogs.ToArray(); BloggingContext.Blogs.Count(); BloggingContext.Blogs.Single(); BloggingContext.Blogs.First();
(3) 將查詢結果資料繫結到 UI
二.LINQ 查詢
Entity Framework Core 使用語言整合查詢 (LINQ) 來查詢資料庫中的資料。 通過 LINQ 可使用 C#(或你選擇的其他 .NET 語言)基於派生上下文和實體類編寫強型別查詢。 LINQ 查詢的表示形式會傳遞給資料庫提供程式,進而轉換為特定的資料庫查詢語言(例如,適用於關係資料庫的 SQL)。
// (1)載入所有資料 var blogs = BloggingContext.Blogs.ToList();
SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b]
//(2)載入單個實體 var blog = BloggingContext.Blogs.Single(b => b.BlogId == 1);
SELECT TOP(2) [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b] WHERE [b].[BlogId] = 1
//(3)篩選 var blogs = BloggingContext.Blogs.Where(b => b.Url.Contains("dotnet")).ToList();
SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b] WHERE CHARINDEX(N'dotnet', [b].[Url]) > 0
//(4)排序 var blogs = BloggingContext.Blogs.OrderByDescending(b => b.BlogId).Select(b=> new { b.BlogId,b.Name }).ToList();
SELECT [b].[BlogId], [b].[Name] FROM [Blogs] AS [b] ORDER BY [b].[BlogId] DESC
//(5) group找出重複的url,取出最大BlogId var blogs = from b in BloggingContext.Blogs group b by new { b.Url} into gs where gs.Count() >1 select new { ID= gs.Max(b=>b.BlogId) }; //top 1 int maxID = blogs.First().ID;
SELECT TOP(1) MAX([b].[BlogId]) AS [ID] FROM [Blogs] AS [b] GROUP BY [b].[Url] HAVING COUNT(*) > 1
// (6)多表join查詢 var query = from b in context.Blogs join p in context.Postsonb.BlogId equals p.BlogId where b.BlogId == 1 select new { b.Name,p.Title } ; var bloglinq= query.ToList();
SELECT [b].[Name], [p].[Title] FROM [Blogs] AS [b] INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId] WHERE [b].[BlogId] = 1
有關顯示 LINQ 可完成的任務的大量示例,請參閱 101 個 LINQ 示例
三. 客戶端求值
EF支援部分查詢在客戶端上求值,而將其他部分推送到資料庫執行。 由資料庫提供程式確定查詢的哪些部分會在資料庫中求值。 下面示例中 客戶端通過執行StandardizeUrl方法來返回URL
,查詢的其餘部分都是在資料庫中執行的。
var blogs = context.Blogs .OrderByDescending(blog => blog.Rating) .Select(blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog.Url) }) .ToList(); public static string StandardizeUrl(string url) { url = url.ToLower(); if (!url.StartsWith("http://")) { url = string.Concat("http://", url); } return url; }
3.1 可能的效能問題
雖然客戶端求值非常有用,但在某些情況下可能會導致效能不佳。 請考慮以下查詢,該where中使用輔助方法。 由於無法在資料庫中執行此操作,因此blog的所有資料將被拉入記憶體中,然後會在客戶端上應用篩選器。 根據資料量以及過濾掉多少資料,可能會導致效能下降。
var blogs = context.Blogs .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet")) .ToList();
3.2 為客戶端評估丟擲異常
預設情況下,當執行客戶端求值時, EF Core 將記錄警告在日誌中。可以改為引發異常或不執行任何操作。 設定如下所示
services.AddDbContext<BloggingContext> (options => options.UseSqlServer(connection) //改為引發異常 .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) );
四. 跟蹤與非跟蹤查詢
跟蹤行為可控制 EF是否將有關實體例項的資訊保留在其更改跟蹤器中。
如果已跟蹤某個實體,則該實體中檢測到的任何更改都會在 SaveChanges()
期間永久儲存到資料庫
。 EF 還會修正從跟蹤查詢中獲取的實體與先前已載入到 DbContext 例項中的實體兩者之間的導航屬性。
4.1 跟蹤查詢
預設情況下,會跟蹤返回實體型別的查詢。
這表示可以更改這些實體例項,然後通過
SaveChanges()
持久化這些更改。在以下示例中,將檢測到對
Blog評分所做的更改,並在
SaveChanges()
期間將這些更改持久化到資料庫中。
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1); blog.Rating = 5; context.SaveChanges(); //顯示設定與上面一樣,開啟了跟蹤查詢 var blog = context.Blogs. AsTracking().SingleOrDefault(b => b.BlogId == 1);
4.2 非跟蹤查詢
只需要讀取資料結果方案時,非跟蹤查詢十分有用。 可以更快速地執行非跟蹤查詢,因為無需設定更改跟蹤資訊。
//設定當前查詢為非跟蹤查詢 var blogs = context.Blogs .AsNoTracking() .ToList(); //還可以在上下文例項級別, 設定預設為非跟蹤查詢 using (var context = new BloggingContext()) { context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var blogs = context.Blogs.ToList(); }
4.3跟蹤和投影
即使查詢的結果型別不是實體型別,只要結果包含實體型別,則預設情況下也會跟蹤這些實體型別。 在以下返回匿名型別的查詢中,會跟蹤結果集中 Blog
的例項。
var blog = context.Blogs .Select(b => new { Blog = b, Posts = b.Posts.Count() });
如果結果集不包含任何實體型別,則不會執行跟蹤。 在以下返回匿名型別(具有實體中的某些值,但沒有實際實體型別的例項)的查詢中,不會執行跟蹤。
var blog = context.Blogs .Select(b => new { Id = b.BlogId, Url = b.Url });
參考文獻