一.查詢的工作原理
Entity Framework Core 使用語(yǔ)言集成查詢 (LINQ) 來(lái)查詢數(shù)據(jù)庫(kù)中的數(shù)據(jù)。 通過(guò) LINQ 可使用 C#(或你選擇的其他 .NET 語(yǔ)言)基于派生上下文和實(shí)體類編寫(xiě)強(qiáng)類型查詢。 LINQ 查詢的表示形式會(huì)傳遞給數(shù)據(jù)庫(kù)提供程序,進(jìn)而轉(zhuǎn)換為特定的數(shù)據(jù)庫(kù)查詢語(yǔ)言(例如,適用于關(guān)系數(shù)據(jù)庫(kù)的 SQL)。
1.1 查詢的生命周期, 下面是每個(gè)查詢所經(jīng)歷的過(guò)程概述:
(1) LINQ 查詢由 E F處理,用于生成已準(zhǔn)備好的表示形式,由數(shù)據(jù)庫(kù)提供程序處理。緩存結(jié)果,以便每次執(zhí)行查詢時(shí)都不需要執(zhí)行此處理。
(2) 結(jié)果將傳遞給數(shù)據(jù)庫(kù)提供程序
a.數(shù)據(jù)庫(kù)提供程序會(huì)識(shí)別出查詢的哪些部分可以在數(shù)據(jù)庫(kù)中求值。
b. 查詢的這些部分會(huì)轉(zhuǎn)換為特定數(shù)據(jù)庫(kù)的查詢語(yǔ)言(例如,關(guān)系數(shù)據(jù)庫(kù)的 SQL)
c. 將一個(gè)或多個(gè)查詢發(fā)送到數(shù)據(jù)庫(kù)并返回結(jié)果集(結(jié)果是來(lái)自數(shù)據(jù)庫(kù)的值,而不是實(shí)體實(shí)例)
(3) 返回結(jié)果集處理
a.如果這是跟蹤查詢,EF會(huì)檢查數(shù)據(jù)是否代表一個(gè)實(shí)體,已存在于上下文實(shí)例的更改跟蹤器中。
如果是,則會(huì)返回現(xiàn)有實(shí)體
如果不是,則會(huì)創(chuàng)建新實(shí)體、設(shè)置更改跟蹤并返回該新實(shí)體
b.如果這是非跟蹤查詢,EF 會(huì)檢查數(shù)據(jù)是否表示此查詢結(jié)果集中的現(xiàn)有實(shí)體
如果是,則會(huì)返回現(xiàn)有實(shí)體
如果不是,則會(huì)創(chuàng)建新實(shí)體并返回該新實(shí)體
1.2 執(zhí)行查詢時(shí):
調(diào)用LINQ運(yùn)算符時(shí),只會(huì)構(gòu)建查詢?cè)趦?nèi)存中的表示形式。 只有在使用結(jié)果時(shí),查詢才會(huì)發(fā)送到數(shù)據(jù)庫(kù)。觸發(fā)查詢發(fā)送到數(shù)據(jù)庫(kù)的最常見(jiàn)操作如下:
(1) 在 for 循環(huán)中循環(huán)訪問(wèn)結(jié)果
var blogs = from b in BloggingContext.Blogs
select {....}
//觸發(fā)數(shù)據(jù)庫(kù)查詢
foreach (var item in blogs)
{
int maxID = item.ID;
}
(2) 使用 ToList、ToArray、Single、Count 等操作都會(huì)觸發(fā)數(shù)據(jù)庫(kù)查詢
BloggingContext.Blogs.ToList();
BloggingContext.Blogs.ToArray();
BloggingContext.Blogs.Count();
BloggingContext.Blogs.Single();
BloggingContext.Blogs.First();
(3) 將查詢結(jié)果數(shù)據(jù)綁定到 UI
二.LINQ 查詢
Entity Framework Core 使用語(yǔ)言集成查詢 (LINQ) 來(lái)查詢數(shù)據(jù)庫(kù)中的數(shù)據(jù)。 通過(guò) LINQ 可使用 C#(或你選擇的其他 .NET 語(yǔ)言)基于派生上下文和實(shí)體類編寫(xiě)強(qiáng)類型查詢。 LINQ 查詢的表示形式會(huì)傳遞給數(shù)據(jù)庫(kù)提供程序,進(jìn)而轉(zhuǎn)換為特定的數(shù)據(jù)庫(kù)查詢語(yǔ)言(例如,適用于關(guān)系數(shù)據(jù)庫(kù)的 SQL)。
// (1)加載所有數(shù)據(jù)
var blogs = BloggingContext.Blogs.ToList();
SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b]
//(2)加載單個(gè)實(shí)體
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 找出重復(fù)的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
有關(guān)顯示 LINQ 可完成的任務(wù)的大量示例,請(qǐng)參閱 101 個(gè) LINQ 示例
三. 客戶端求值
EF支持部分查詢?cè)诳蛻舳松锨笾担鴮⑵渌糠滞扑偷綌?shù)據(jù)庫(kù)執(zhí)行。 由數(shù)據(jù)庫(kù)提供程序確定查詢的哪些部分會(huì)在數(shù)據(jù)庫(kù)中求值。 下面示例中 客戶端通過(guò)執(zhí)行StandardizeUrl方法來(lái)返回 URL,查詢的其余部分都是在數(shù)據(jù)庫(kù)中執(zhí)行的。
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 可能的性能問(wèn)題
雖然客戶端求值非常有用,但在某些情況下可能會(huì)導(dǎo)致性能不佳。 請(qǐng)考慮以下查詢,該where中使用輔助方法。 由于無(wú)法在數(shù)據(jù)庫(kù)中執(zhí)行此操作,因此blog的所有數(shù)據(jù)將被拉入內(nèi)存中,然后會(huì)在客戶端上應(yīng)用篩選器。 根據(jù)數(shù)據(jù)量以及過(guò)濾掉多少數(shù)據(jù),可能會(huì)導(dǎo)致性能下降。
var blogs = context.Blogs
.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
.ToList();
3.2 為客戶端評(píng)估拋出異常
默認(rèn)情況下,當(dāng)執(zhí)行客戶端求值時(shí),EF Core 將記錄警告在日志中??梢愿臑橐l(fā)異?;虿粓?zhí)行任何操作。 設(shè)置如下所示
services.AddDbContext<BloggingContext>
(options =>
options.UseSqlServer(connection)
//改為引發(fā)異常
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
);
四. 跟蹤與非跟蹤查詢
跟蹤行為可控制 EF是否將有關(guān)實(shí)體實(shí)例的信息保留在其更改跟蹤器中。 如果已跟蹤某個(gè)實(shí)體,則該實(shí)體中檢測(cè)到的任何更改都會(huì)在 SaveChanges() 期間永久保存到數(shù)據(jù)庫(kù)。 EF 還會(huì)修正從跟蹤查詢中獲取的實(shí)體與先前已加載到 DbContext 實(shí)例中的實(shí)體兩者之間的導(dǎo)航屬性。
4.1 跟蹤查詢
默認(rèn)情況下,會(huì)跟蹤返回實(shí)體類型的查詢。 這表示可以更改這些實(shí)體實(shí)例,然后通過(guò) SaveChanges() 持久化這些更改。在以下示例中,將檢測(cè)到對(duì)Blog評(píng)分所做的更改,并在 SaveChanges() 期間將這些更改持久化到數(shù)據(jù)庫(kù)中。
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();
//顯示設(shè)置與上面一樣,開(kāi)啟了跟蹤查詢
var blog = context.Blogs. AsTracking().SingleOrDefault(b => b.BlogId == 1);
4.2 非跟蹤查詢
只需要讀取數(shù)據(jù)結(jié)果方案時(shí),非跟蹤查詢十分有用。 可以更快速地執(zhí)行非跟蹤查詢,因?yàn)闊o(wú)需設(shè)置更改跟蹤信息。
//設(shè)置當(dāng)前查詢?yōu)榉歉櫜樵? var blogs = context.Blogs
.AsNoTracking()
.ToList();
//還可以在上下文實(shí)例級(jí)別, 設(shè)置默認(rèn)為非跟蹤查詢
using (var context = new BloggingContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = context.Blogs.ToList();
}
4.3跟蹤和投影
即使查詢的結(jié)果類型不是實(shí)體類型,只要結(jié)果包含實(shí)體類型,則默認(rèn)情況下也會(huì)跟蹤這些實(shí)體類型。 在以下返回匿名類型的查詢中,會(huì)跟蹤結(jié)果集中 Blog 的實(shí)例。
var blog = context.Blogs
.Select(b =>
new
{
Blog = b,
Posts = b.Posts.Count()
});
如果結(jié)果集不包含任何實(shí)體類型,則不會(huì)執(zhí)行跟蹤。 在以下返回匿名類型(具有實(shí)體中的某些值,但沒(méi)有實(shí)際實(shí)體類型的實(shí)例)的查詢中,不會(huì)執(zhí)行跟蹤。
var blog = context.Blogs
.Select(b =>
new
{
Id = b.BlogId,
Url = b.Url
});
參考文獻(xiàn)