一.查詢的工作原理 Entity Framework Core 使用語言集成查詢 (LINQ) 來查詢資料庫中的數據。 通過 LINQ 可使用 C#(或你選擇的其他 .NET 語言)基於派生上下文和實體類編寫強類型查詢。 LINQ 查詢的表示形式會傳遞給資料庫提供程式,進而轉換為特定的資料庫查詢語言 ...
一.查詢的工作原理
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.Posts on b.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 });
參考文獻