推薦必讀:EF Core 開發(fā)實踐規(guī)范
嚴(yán)重問題
客戶端求值
如where條件包含的GetValueOrDefault()不能被翻譯成sql語句
不規(guī)范代碼段例子
public async Task<List<Person>> GetPersonsAsync()
{
var results = await _context.Person
.Where(p => p.State.GetValueOrDefault() == 1)
.ToListAsync()
return results;
}客戶端與服務(wù)器評估
作為一般規(guī)則,Entity Framework Core 會嘗試盡可能全面地評估服務(wù)器上的查詢。EF Core 將查詢的一部分轉(zhuǎn)換為可在客戶端評估的參數(shù)。系統(tǒng)將查詢的其余部分(及生成的參數(shù))提供給數(shù)據(jù)庫提供程序,以確定要在服務(wù)器上評估的等效數(shù)據(jù)庫查詢。EF Core 支持在頂級投影中進行部分客戶端評估(基本上為最后一次調(diào)用 Select())。如果查詢中的頂級投影無法轉(zhuǎn)換為服務(wù)器,EF Core 將從服務(wù)器中提取任何所需的數(shù)據(jù),并在客戶端上評估查詢的其余部分。如果 EF Core 在頂級投影之外的任何位置檢測到不能轉(zhuǎn)換為服務(wù)器的表達式,則會引發(fā)運行時異常。請參閱查詢工作原理,了解 EF Core 如何確定哪些表達式無法轉(zhuǎn)換為服務(wù)器。
在 3.0 版之前,Entity Framework Core 支持在查詢中的任何位置進行客戶端評估。
頂級投影中的客戶端評估
在下面的示例中,一個輔助方法用于標(biāo)準(zhǔn)化從 SQL Server 數(shù)據(jù)庫中返回的博客的 URL。由于 SQL Server 提供程序不了解此方法的實現(xiàn)方式,因此無法將其轉(zhuǎn)換為 SQL。查詢的所有其余部分是在數(shù)據(jù)庫中評估的,但通過此方法傳遞返回的 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;
}不支持的客戶端評估
盡管客戶端評估非常有用,但有時會減弱性能。請看以下查詢,其中的 where 篩選器現(xiàn)已使用輔助方法。由于數(shù)據(jù)庫中不能應(yīng)用篩選器,因此需要將所有數(shù)據(jù)提取到內(nèi)存中,以便在客戶端上應(yīng)用篩選器。根據(jù)服務(wù)器上的篩選器和數(shù)據(jù)量,客戶端評估可能會減弱性能。因此 Entity Framework Core 會阻止此類客戶端評估,并引發(fā)運行時異常。
var blogs = context.Blogs
.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
.ToList();顯式客戶端評估
在某些情況下,可能需要以顯式方式強制進行客戶端評估,如下所示
由于數(shù)據(jù)量小,因此在進行客戶端評估時才不會大幅減弱性能。
所用的 LINQ 運算符不會進行任何服務(wù)器端轉(zhuǎn)換。
在這種情況下,通過調(diào)用 AsEnumerable 或 ToList 等方法(若為異步,則調(diào)用 AsAsyncEnumerable 或 ToListAsync),以顯式方式選擇進行客戶端評估。使用 AsEnumerable 將對結(jié)果進行流式傳輸,但使用 ToList 將通過創(chuàng)建列表來進行緩沖,因此也會占用額外的內(nèi)存。但如果枚舉多次,則將結(jié)果存儲到列表中可以帶來更大的幫助,因為只有一個對數(shù)據(jù)庫的查詢。根據(jù)具體的使用情況,你應(yīng)該評估哪種方法更適合。
var blogs = context.Blogs
.AsEnumerable()
.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
.ToList();客戶端評估中潛在的內(nèi)存泄漏
由于查詢轉(zhuǎn)換和編譯的開銷高昂,因此 EF Core 會緩存已編譯的查詢計劃。緩存的委托在對頂級投影進行客戶端評估時可能會使用客戶端代碼。
EF Core 為樹型結(jié)構(gòu)中客戶端評估的部分生成參數(shù),并通過替換參數(shù)值重用查詢計劃。但表達式樹中的某些常數(shù)無法轉(zhuǎn)換為參數(shù)。如果緩存的委托包含此類常數(shù),則無法將這些對象垃圾回收,因為它們?nèi)员灰谩?/span>
如果此類對象包含 DbContext 或其中的其他服務(wù),則會導(dǎo)致應(yīng)用的內(nèi)存使用量逐漸增多。此行為通常是內(nèi)存泄漏的標(biāo)志。只要遇到的常數(shù)為不能使用當(dāng)前數(shù)據(jù)庫提供程序映射的類型,EF Core 就會引發(fā)異常。常見原因及其解決方案如下所示:
使用實例方法:在客戶端投影中使用實例方法時,表達式樹包含實例的常數(shù)。如果你的方法不使用該實例中的任何數(shù)據(jù),請考慮將該方法設(shè)為靜態(tài)方法。如果需要方法主體中的實例數(shù)據(jù),則將特定數(shù)據(jù)作為實參傳遞給方法。
將常數(shù)實參傳遞給方法:這種情況通常是由于在客戶端方法的實參中使用 this 引起的。請考慮將實參拆分為多個標(biāo)量實參,可由數(shù)據(jù)庫提供程序進行映射。
其他常數(shù):如果在任何其他情況下都出現(xiàn)常數(shù),則可以評估在處理過程中是否需要該常數(shù)。如果必須具有常數(shù),或者如果無法使用上述情況中的解決方案,則創(chuàng)建本地變量來存儲值,并在查詢中使用局部變量。EF Core 會將局部變量轉(zhuǎn)換為形參。
建議解決
無用追蹤
無須追蹤的數(shù)據(jù)沒有加AsNoTracking
跟蹤與非跟蹤查詢
跟蹤行為決定了 Entity Framework Core 是否將有關(guān)實體實例的信息保留在其更改跟蹤器中。如果已跟蹤某個實體,則該實體中檢測到的任何更改都會在 SaveChanges() 期間永久保存到數(shù)據(jù)庫。EF Core 還將修復(fù)跟蹤查詢結(jié)果中的實體與更改跟蹤器中的實體之間的導(dǎo)航屬性。
從不跟蹤無鍵實體類型。無論在何處提到實體類型,它都是指定義了鍵的實體類型。
跟蹤查詢
返回實體類型的查詢是默認(rèn)會被跟蹤的。這表示可以更改這些實體實例,然后通過 SaveChanges() 持久化這些更改。在以下示例中,將檢測到對博客評分所做的更改,并在 SaveChanges() 期間將這些更改持久化到數(shù)據(jù)庫中。
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();非跟蹤查詢
在只讀方案中使用結(jié)果時,非跟蹤查詢十分有用。可以更快速地執(zhí)行非跟蹤查詢,因為無需設(shè)置更改跟蹤信息。如果不需要更新從數(shù)據(jù)庫中檢索到的實體,則應(yīng)使用非跟蹤查詢。可以將單個查詢替換為非跟蹤查詢。
var blogs = context.Blogs
.AsNoTracking()
.ToList();還可以在上下文實例級別更改默認(rèn)跟蹤行為:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = context.Blogs.ToList();標(biāo)識解析
由于跟蹤查詢使用更改跟蹤器,因此 EF Core 將在跟蹤查詢中執(zhí)行標(biāo)識解析。當(dāng)具體化實體時,如果 EF Core 已被跟蹤,則會從更改跟蹤器返回相同的實體實例。如果結(jié)果中多次包含相同的實體,則每次會返回相同的實例。
非跟蹤查詢不會使用更改跟蹤器,也不會執(zhí)行標(biāo)識解析。因此會返回實體的新實例,即使結(jié)果中多次包含相同的實體也是如此。此行為與 EF Core 3.0 之前的版本中的行為有所不同,請參閱早期版本。
跟蹤和自定義投影
即使查詢的結(jié)果類型不是實體類型,默認(rèn)情況下 EF Core 也會跟蹤結(jié)果中包含的實體類型。在以下返回匿名類型的查詢中,結(jié)果集中的 Blog 實例會被跟蹤。
var blog = context.Blogs
.Select(b =>
new
{
Blog = b,
PostCount = b.Posts.Count()
});如果結(jié)果集包含來自 LINQ 組合的實體類型,EF Core 將跟蹤它們。
var blog = context.Blogs
.Select(b =>
new
{
Blog = b,
Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault()
});如果結(jié)果集不包含任何實體類型,則不會執(zhí)行跟蹤。在以下查詢中,我們返回匿名類型(具有實體中的某些值,但沒有實際實體類型的實例)。查詢中沒有任何被跟蹤的實體。
var blog = context.Blogs
.Select(b =>
new
{
Id = b.BlogId,
Url = b.Url
});EF Core 支持執(zhí)行頂級投影中的客戶端評估。如果 EF Core 具體化實體實例以進行客戶端評估,則會跟蹤該實體實例。
此處,由于我們要將 blog 實體傳遞到客戶端方法 StandardizeURL,因此 EF Core 也會跟蹤博客實例。
var blogs = context.Blogs
.OrderByDescending(blog => blog.Rating)
.Select(blog => new
{
Id = blog.BlogId,
Url = StandardizeUrl(blog)
})
.ToList();
public static string StandardizeUrl(Blog blog)
{
var url = blog.Url.ToLower();
if (!url.StartsWith("http://"))
{
url = string.Concat("http://", url);
}
return url;
}EF Core 不會跟蹤結(jié)果中包含的無鍵實體實例。但 EF Core 會根據(jù)上述規(guī)則跟蹤帶有鍵的實體類型的所有其他實例。
在 EF Core 3.0 之前,某些上述規(guī)則的工作方式有所不同。有關(guān)詳細信息,請參閱早期版本。
沒有使用異步方法
沒有優(yōu)先使用異步方法
不規(guī)范代碼段例子
public async Task<int> AddPersons(IEnumerable<Person> persons)
{
this._context.Person.AddRange(persons);
return await this._context.SaveChangesAsync();
}異步查詢
當(dāng)在數(shù)據(jù)庫中執(zhí)行查詢時,異步查詢可避免阻止線程。異步查詢對于在胖客戶端應(yīng)用程序中保持響應(yīng)式 UI 非常重要。異步查詢還可以增加 Web 應(yīng)用程序中的吞吐量,即通過釋放線程,以處理 Web 應(yīng)用程序中的其他請求。有關(guān)詳細信息,請參閱使用 C# 異步編程。
EF Core 不支持在同一上下文實例上運行多個并行操作。應(yīng)始終等待操作完成,然后再開始下一個操作。這通常是通過在每個異步操作上使用 await 關(guān)鍵字完成的。
Entity Framework Core 提供一組類似于 LINQ 方法的異步擴展方法,用于執(zhí)行查詢并返回結(jié)果。示例包括 ToListAsync()、ToArrayAsync()、SingleAsync()。某些 LINQ 運算符(如 Where(...) 或 OrderBy(...))沒有對應(yīng)的異步版本,因為這些方法僅用于構(gòu)建 LINQ 表達式樹,而不會導(dǎo)致在數(shù)據(jù)庫中執(zhí)行查詢。
EF Core 異步擴展方法在 Microsoft.EntityFrameworkCore 命名空間中定義 。必須導(dǎo)入此命名空間才能使這些方法可用。
public async Task<List<Blog>> GetBlogsAsync()
{
using (var context = new BloggingContext())
{
return await context.Blogs.ToListAsync();
}
}事務(wù)濫用
沒必要使用事務(wù)的場景使用事務(wù)
不規(guī)范代碼段例子
public async Task<bool> UpdatePersonInfo(List<Person> persons, List<Address> addresses)
{
using (var transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
try
{
_dbContext.Person.UpdateRange(persons);
await _dbContext.SaveChangesAsync();
_dbContext.Address.UpdateRange(addresses);
await _dbContext.SaveChangesAsync();
transaction.Commit();
return true;
}
catch (Exception ex)
{
transaction.Rollback();
throw new InternalServerErrorException($"更新失敗,ErrorMessage:{ex.Message}
InnerException:{ex.InnerException}", ex);
}
}
}使用事務(wù)
事務(wù)允許以原子方式處理多個數(shù)據(jù)庫操作。如果已提交事務(wù),則所有操作都會成功應(yīng)用到數(shù)據(jù)庫。如果已回滾事務(wù),則所有操作都不會應(yīng)用到數(shù)據(jù)庫。
默認(rèn)事務(wù)行為
默認(rèn)情況下,如果數(shù)據(jù)庫提供程序支持事務(wù),則會在單次調(diào)用 SaveChanges() 時將所有更改都將應(yīng)用到事務(wù)中。如果其中有任何更改失敗,則會回滾事務(wù)且所有更改都不會應(yīng)用到數(shù)據(jù)庫。這意味著,SaveChanges() 可保證要么完全成功,要么在出現(xiàn)錯誤時不修改數(shù)據(jù)庫。
對于大多數(shù)應(yīng)用程序,此默認(rèn)行為已足夠。除非應(yīng)用程序確有需求,否則不應(yīng)手動控制事務(wù)。
控制事務(wù)
可以使用 DbContext.Database API 開始、提交和回滾事務(wù)。以下示例顯示了在單個事務(wù)中執(zhí)行的兩個 SaveChanges() 操作以及 一個LINQ 查詢。
并非所有數(shù)據(jù)庫提供程序都支持事務(wù)。調(diào)用事務(wù) API 時,某些提供程序可能會引發(fā)異常或不執(zhí)行任何操作。
規(guī)范參考
數(shù)據(jù)追蹤參考規(guī)范
https://docs.microsoft.com/zh-cn/ef/core/querying/tracking
客戶端求值參考規(guī)范
https://docs.microsoft.com/zh-cn/ef/core/querying/client-eval
異步查詢參考規(guī)范
https://docs.microsoft.com/zh-cn/ef/core/querying/async
加載相關(guān)數(shù)據(jù)參考規(guī)范
https://docs.microsoft.com/zh-cn/ef/core/querying/related-data
事務(wù)使用參考規(guī)范
https://docs.microsoft.com/zh-cn/ef/core/saving/transactions
github:https://github.com/cailin0630


又來一個神奇的網(wǎng)站!

谷歌靈魂插件,98%的程序員都好評!
