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