嚴重問題
用戶端求值
- 如where條件包含的GetValueOrDefault()不能被翻譯成sql語句
- 不規範代碼段例子
public async Task<List<Person>> GetPersonsAsync()
{
var results = await _context.Person
.Where(p => p.State.GetValueOrDefault() == 1)
.ToListAsync()
return results;
}
用戶端與伺服器評估
作為一般規則,Entity Framework Core 會嘗試盡可能全面地評估伺服器上的查詢。 EF Core 将查詢的一部分轉換為可在用戶端評估的參數。 系統将查詢的其餘部分(及生成的參數)提供給資料庫提供程式,以确定要在伺服器上評估的等效資料庫查詢。 EF Core 支援在***投影中進行部分用戶端評估(基本上為最後一次調用 Select())。 如果查詢中的***投影無法轉換為伺服器,EF Core 将從伺服器中提取任何所需的資料,并在用戶端上評估查詢的其餘部分。 如果 EF Core 在***投影之外的任何位置檢測到不能轉換為伺服器的表達式,則會引發運作時異常。 請參閱查詢工作原理,了解 EF Core 如何确定哪些表達式無法轉換為伺服器。
在 3.0 版之前,Entity Framework Core 支援在查詢中的任何位置進行用戶端評估。
***投影中的用戶端評估
在下面的示例中,一個輔助方法用于标準化從 SQL Server 資料庫中傳回的部落格的 URL。 由于 SQL Server 提供程式不了解此方法的實作方式,是以無法将其轉換為 SQL。 查詢的所有其餘部分是在資料庫中評估的,但通過此方法傳遞傳回的 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 篩選器現已使用輔助方法。 由于資料庫中不能應用篩選器,是以需要将所有資料提取到記憶體中,以便在用戶端上應用篩選器。 根據伺服器上的篩選器和資料量,用戶端評估可能會減弱性能。 是以 Entity Framework Core 會阻止此類用戶端評估,并引發運作時異常。
var blogs = context.Blogs
.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
.ToList();
顯式用戶端評估
在某些情況下,可能需要以顯式方式強制進行用戶端評估,如下所示
由于資料量小,是以在進行用戶端評估時才不會大幅減弱性能。
所用的 LINQ 運算符不會進行任何伺服器端轉換。
在這種情況下,通過調用 AsEnumerable 或 ToList 等方法(若為異步,則調用 AsAsyncEnumerable 或 ToListAsync),以顯式方式選擇進行用戶端評估。 使用 AsEnumerable 将對結果進行流式傳輸,但使用 ToList 将通過建立清單來進行緩沖,是以也會占用額外的記憶體。 但如果枚舉多次,則将結果存儲到清單中可以帶來更大的幫助,因為隻有一個對資料庫的查詢。 根據具體的使用情況,你應該評估哪種方法更适合。
var blogs = context.Blogs
.AsEnumerable()
.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
.ToList();
用戶端評估中潛在的記憶體洩漏
由于查詢轉換和編譯的開銷高昂,是以 EF Core 會緩存已編譯的查詢計劃。 緩存的委托在對***投影進行用戶端評估時可能會使用用戶端代碼。 EF Core 為樹型結構中用戶端評估的部分生成參數,并通過替換參數值重用查詢計劃。 但表達式樹中的某些常數無法轉換為參數。 如果緩存的委托包含此類常數,則無法将這些對象垃圾回收,因為它們仍被引用。 如果此類對象包含 DbContext 或其中的其他服務,則會導緻應用的記憶體使用量逐漸增多。 此行為通常是記憶體洩漏的标志。 隻要遇到的常數為不能使用目前資料庫提供程式映射的類型,EF Core 就會引發異常。 常見原因及其解決方案如下所示:
使用執行個體方法:在用戶端投影中使用執行個體方法時,表達式樹包含執行個體的常數。 如果你的方法不使用該執行個體中的任何資料,請考慮将該方法設為靜态方法。 如果需要方法主體中的執行個體資料,則将特定資料作為實參傳遞給方法。
将常數實參傳遞給方法:這種情況通常是由于在用戶端方法的實參中使用 this 引起的。 請考慮将實參拆分為多個标量實參,可由資料庫提供程式進行映射。
其他常數:如果在任何其他情況下都出現常數,則可以評估在處理過程中是否需要該常數。 如果必須具有常數,或者如果無法使用上述情況中的解決方案,則建立本地變量來存儲值,并在查詢中使用局部變量。 EF Core 會将局部變量轉換為形參。
建議解決
無用追蹤
- 無須追蹤的資料沒有加AsNoTracking
跟蹤與非跟蹤查詢
跟蹤行為決定了 Entity Framework Core 是否将有關實體執行個體的資訊保留在其更改跟蹤器中。 如果已跟蹤某個實體,則該實體中檢測到的任何更改都會在 SaveChanges() 期間永久儲存到資料庫。 EF Core 還将修複跟蹤查詢結果中的實體與更改跟蹤器中的實體之間的導航屬性。
從不跟蹤無鍵實體類型。 無論在何處提到實體類型,它都是指定義了鍵的實體類型。
跟蹤查詢
傳回實體類型的查詢是預設會被跟蹤的。 這表示可以更改這些實體執行個體,然後通過 SaveChanges() 持久化這些更改。 在以下示例中,将檢測到對部落格評分所做的更改,并在 SaveChanges() 期間将這些更改持久化到資料庫中。
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();
非跟蹤查詢
在隻讀方案中使用結果時,非跟蹤查詢十分有用。 可以更快速地執行非跟蹤查詢,因為無需設定更改跟蹤資訊。 如果不需要更新從資料庫中檢索到的實體,則應使用非跟蹤查詢。 可以将單個查詢替換為非跟蹤查詢。
var blogs = context.Blogs
.AsNoTracking()
.ToList();
還可以在上下文執行個體級别更改預設跟蹤行為:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = context.Blogs.ToList();
辨別解析
由于跟蹤查詢使用更改跟蹤器,是以 EF Core 将在跟蹤查詢中執行辨別解析。 當具體化實體時,如果 EF Core 已被跟蹤,則會從更改跟蹤器傳回相同的實體執行個體。 如果結果中多次包含相同的實體,則每次會傳回相同的執行個體。 非跟蹤查詢不會使用更改跟蹤器,也不會執行辨別解析。 是以會傳回實體的新執行個體,即使結果中多次包含相同的實體也是如此。 此行為與 EF Core 3.0 之前的版本中的行為有所不同,請參閱早期版本。
跟蹤和自定義投影
即使查詢的結果類型不是實體類型,預設情況下 EF Core 也會跟蹤結果中包含的實體類型。 在以下傳回匿名類型的查詢中,結果集中的 Blog 執行個體會被跟蹤。
var blog = context.Blogs
.Select(b =>
new
{
Blog = b,
PostCount = b.Posts.Count()
});
如果結果集包含來自 LINQ 組合的實體類型,EF Core 将跟蹤它們。
var blog = context.Blogs
.Select(b =>
new
{
Blog = b,
Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault()
});
如果結果集不包含任何實體類型,則不會執行跟蹤。 在以下查詢中,我們傳回匿名類型(具有實體中的某些值,但沒有實際實體類型的執行個體)。 查詢中沒有任何被跟蹤的實體。
var blog = context.Blogs
.Select(b =>
new
{
Id = b.BlogId,
Url = b.Url
});
EF Core 支援執行***投影中的用戶端評估。 如果 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 不會跟蹤結果中包含的無鍵實體執行個體。 但 EF Core 會根據上述規則跟蹤帶有鍵的實體類型的所有其他執行個體。
在 EF Core 3.0 之前,某些上述規則的工作方式有所不同。 有關詳細資訊,請參閱早期版本。
沒有使用異步方法
- 沒有優先使用異步方法
- 不規範代碼段例子
public async Task<int> AddPersons(IEnumerable<Person> persons)
{
this._context.Person.AddRange(persons);
return await this._context.SaveChangesAsync();
}
異步查詢
當在資料庫中執行查詢時,異步查詢可避免阻止線程。 異步查詢對于在胖用戶端應用程式中保持響應式 UI 非常重要。 異步查詢還可以增加 Web 應用程式中的吞吐量,即通過釋放線程,以處理 Web 應用程式中的其他請求。 有關詳細資訊,請參閱使用 C# 異步程式設計。
EF Core 不支援在同一上下文執行個體上運作多個并行操作。 應始終等待操作完成,然後再開始下一個操作。 這通常是通過在每個異步操作上使用 await 關鍵字完成的。
Entity Framework Core 提供一組類似于 LINQ 方法的異步擴充方法,用于執行查詢并傳回結果。 示例包括 ToListAsync()、ToArrayAsync()、SingleAsync()。 某些 LINQ 運算符(如 Where(...) 或 OrderBy(...))沒有對應的異步版本,因為這些方法僅用于建構 LINQ 表達式樹,而不會導緻在資料庫中執行查詢。
EF Core 異步擴充方法在 Microsoft.EntityFrameworkCore 命名空間中定義 。 必須導入此命名空間才能使這些方法可用。
public async Task<List<Blog>> GetBlogsAsync()
{
using (var context = new BloggingContext())
{
return await context.Blogs.ToListAsync();
}
}
事務濫用
- 沒必要使用事務的場景使用事務
- 不規範代碼段例子
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}\r\nInnerException:{ex.InnerException}", ex);
}
}
}
使用事務
事務允許以原子方式處理多個資料庫操作。 如果已送出事務,則所有操作都會成功應用到資料庫。 如果已復原事務,則所有操作都不會應用到資料庫。
預設事務行為
預設情況下,如果資料庫提供程式支援事務,則會在單次調用 SaveChanges() 時将所有更改都将應用到事務中。 如果其中有任何更改失敗,則會復原事務且所有更改都不會應用到資料庫。 這意味着,SaveChanges() 可保證要麼完全成功,要麼在出現錯誤時不修改資料庫。
對于大多數應用程式,此預設行為已足夠。 除非應用程式确有需求,否則不應手動控制事務。
控制事務
可以使用 DbContext.Database API 開始、送出和復原事務。 以下示例顯示了在單個事務中執行的兩個 SaveChanges() 操作以及 一個LINQ 查詢。
并非所有資料庫提供程式都支援事務。 調用事務 API 時,某些提供程式可能會引發異常或不執行任何操作。
規範參考
資料追蹤參考規範: https://docs.microsoft.com/zh-cn/ef/core/querying/tracking
用戶端求值參考規範:https://docs.microsoft.com/zh-cn/ef/core/querying/client-eval
異步查詢參考規範:https://docs.microsoft.com/zh-cn/ef/core/querying/async
加載相關資料參考規範:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data
事務使用參考規範:https://docs.microsoft.com/zh-cn/ef/core/saving/transactions