本主題描述 ADO.NET Entity Framework 的效能特性,並提供一些考慮來協助改善 Entity Framework 應用程式的效能。
查詢執行的階段
為了更好地了解 Entity Framework 中查詢的效能,理解查詢在針對概念模型執行時所進行的操作,以及如何將數據作為對象返回,將會非常有幫助。 下表描述這一系列的作業。
| 行動 | 相對成本 | 頻率 | 評論 |
|---|---|---|---|
| 載入元數據 | 溫和 | 在每個應用程式域中執行一次。 | Entity Framework 所使用的模型和對應元數據會載入至 MetadataWorkspace。 此元數據會全域快取,並可供相同應用程式域中的其他 實例 ObjectContext 使用。 |
| 開啟資料庫連線 | 中度1 | 視需要。 | 因為對資料庫的開啟連接會耗用寶貴的資源,因此 Entity Framework 只會視需要開啟並關閉資料庫連接。 您也可以明確開啟連線。 如需詳細資訊,請參閱 管理連線和交易。 |
| 生成視圖 | 高 | 在每個應用程式域中執行一次。 (可以預先產生。 | 在 Entity Framework 可以針對概念模型執行查詢或儲存數據源的變更之前,它必須產生一組本機查詢檢視來存取資料庫。 由於產生這些檢視的成本很高,因此您可以預先產生檢視,並在設計時間將它們新增至專案。 如需詳細資訊,請參閱 如何:預先產生檢視以改善查詢效能。 |
| 準備查詢 | 中度2 | 對於每個唯一查詢,都執行一次。 | 包含撰寫查詢命令的成本、根據模型和對應元數據產生命令樹狀結構,以及定義傳回數據的圖形。 因為現在會快取 Entity SQL 查詢命令和 LINQ 查詢,因此稍後相同的查詢執行會花費較少的時間。 您仍然可以使用已編譯的 LINQ 查詢來降低後續執行中的此成本,而編譯的查詢比自動快取的 LINQ 查詢更有效率。 如需詳細資訊,請參閱編譯的查詢(LINQ to Entities)。 如需 LINQ 查詢執行的一般資訊,請參閱 LINQ to Entities。
注意: 不會自動快取將運算子套用 Enumerable.Contains 至記憶體內部集合的 LINQ to Entities 查詢。 此外,不允許在編譯的 LINQ 查詢中參數化記憶體內部集合。 |
| 執行查詢 | 低2 | 每個查詢一次。 | 使用 ADO.NET 數據提供者,對數據源執行命令的成本。 由於大部分數據源會快取查詢計劃,因此稍後執行相同的查詢可能需要較少的時間。 |
| 載入和驗證類型 | 低3 | 針對每個 ObjectContext 實例一次。 | 系統會根據概念模型所定義的型別載入和驗證類型。 |
| 追蹤 | 低3 | 對於查詢傳回的每個物件,執行一次。 4 | 如果查詢使用 NoTracking 合併選項,這個階段不會影響效能。 如果查詢使用 AppendOnly、 PreserveChanges或 OverwriteChanges 合併選項,則會在 中 ObjectStateManager追蹤查詢結果。 每個查詢傳回的追蹤物件都會產生EntityKey,用來在ObjectStateEntry中建立ObjectStateManager。 如果找到現有的 ObjectStateEntry 針對 EntityKey,則會傳回現有的物件。 如果使用 PreserveChanges 或 OverwriteChanges 選項,則會在傳回物件之前更新物件。 如需詳細資訊,請參閱 身分識別解析、狀態管理和變更追蹤。 |
| 物件的具體化 | 中度3 | 對於查詢傳回的每個物件,執行一次。 4 | 根據返回的DbDataReader物件的值進行讀取,然後建立物件,並依據DbDataRecord類別每個實例中的值設定屬性值。 如果物件已存在於 中 ObjectContext ,且查詢使用 AppendOnly 或 PreserveChanges 合併選項,則這個階段不會影響效能。 如需詳細資訊,請參閱 身分識別解析、狀態管理和變更追蹤。 |
1 當數據源提供者實作連線共用時,開啟連線的成本會分散到集區。 .NET Provider for SQL Server 支持連線共用。
2 成本隨著查詢複雜度增加而增加。
3 總成本會與查詢所傳回的物件數目成正比。
4 EntityClient 查詢不需要此額外負荷,因為 EntityClient 查詢會傳回 而不是 EntityDataReader 物件。 如需詳細資訊,請參閱 EntityClient 提供者的 Entity Framework。
其他考慮
以下是可能會影響 Entity Framework 應用程式效能的其他考慮。
查詢執行
因為查詢可能會耗用大量資源,請考慮程式代碼中的哪個時間點,以及執行查詢的計算機。
延遲與立即執行
當您建立 ObjectQuery<T> 或 LINQ 查詢時,可能不會立即執行查詢。 查詢的執行會延後,直到需要結果時才執行,例如在進行 foreach(C#)或 For Each(Visual Basic) 列舉,或者指派來填充 List<T> 集合時。 當您在 Execute 上呼叫 ObjectQuery<T> 方法或呼叫會傳回單一查詢的 LINQ 方法時,查詢執行會立即開始,例如 First 或 Any。 如需詳細資訊,請參閱對象查詢和查詢執行(LINQ to Entities)。
客戶端執行 LINQ 查詢
雖然 LINQ 查詢的執行發生在裝載數據源的電腦上,但用戶端電腦上可能會評估 LINQ 查詢的某些部分。 如需詳細資訊,請參閱 查詢執行(LINQ to Entities)部分之中的資料儲存執行。
查詢和映射複雜度
個別查詢和實體模型中對應的複雜性會對查詢效能產生重大影響。
映射複雜性
在概念模型和儲存模型中,當實體之間的對應關係超越簡單的一對一映射時,比那些一對一對應的模型會產生更複雜的命令。
查詢複雜度
在針對數據源執行的命令中,需要大量聯結的查詢,或傳回大量數據的查詢,可能會以下列方式影響效能:
針對看似簡單的概念模型進行查詢,可能會導致對數據源執行更複雜的查詢。 這可能會發生,因為 Entity Framework 會將對概念模型的查詢轉換成對數據源的相應查詢。 當概念模型中的單一實體集對應至數據源中的多個數據表,或實體之間的關聯性對應至聯結數據表時,針對數據源查詢執行的查詢命令可能需要一或多個聯結。
備註
使用 ToTraceString 方法 或 ObjectQuery<T> 類別的 EntityCommand 方法,檢視針對指定查詢的數據源所執行的命令。 如需詳細資訊,請參閱 如何:檢視存放區命令。
巢狀實體 SQL 查詢可能會在伺服器上建立聯結,而且可以傳回大量的數據列。
以下是投影子句中巢狀查詢的範例:
SELECT c, (SELECT c, (SELECT c FROM AdventureWorksModel.Vendor AS c ) As Inner2 FROM AdventureWorksModel.JobCandidate AS c ) As Inner1 FROM AdventureWorksModel.EmployeeDepartmentHistory AS c此外,這類查詢會導致查詢管線產生單一查詢,並在巢狀查詢之間重複物件。 因此,單一數據行可能會重複多次。 在某些資料庫上,包括 SQL Server,這可能會導致 TempDB 數據表成長非常龐大,進而降低伺服器效能。 當您執行巢狀查詢時,應該小心。
如果用戶端正在以與結果集大小成正比的方式取用資源的作業,則傳回大量數據的任何查詢都可能會導致效能降低。 在這種情況下,您應該考慮限制查詢所傳回的數據量。 如需詳細資訊,請參閱 如何:逐頁查看查詢結果。
Entity Framework 自動產生的任何命令可能比資料庫開發人員明確撰寫的類似命令更為複雜。 如果您需要明確控制針對數據源執行的命令,請考慮定義數據表值函式或預存程序的對應。
關係
為了獲得最佳查詢效能,您必須將實體之間的關聯性定義為實體模型中的關聯性,以及定義為數據源中的邏輯關聯性。
查詢路徑
根據預設,當您執行 ObjectQuery<T>時,不會傳回相關物件(雖然代表關聯性本身的物件為 )。 您可以使用下列三種方式之一載入相關物件:
在執行ObjectQuery<T>之前,設定查詢路徑。
在對象公開的導覽屬性上呼叫
Load方法。將 LazyLoadingEnabled 上的 ObjectContext 選項設定為
true。 請注意,當您使用 實體數據模型設計工具產生物件層程序代碼時,系統會自動完成此動作。 如需詳細資訊,請參閱 產生的程式代碼概觀。
當您考慮要使用哪一個選項時,請注意,針對資料庫的要求數目與單一查詢中傳回的數據量之間會有取捨。 如需詳細資訊,請參閱 載入相關物件。
使用查詢路徑
查詢路徑會定義查詢傳回的物件圖形。 當您定義查詢路徑時,只需要針對資料庫提出單一要求,才能傳回路徑所定義的所有物件。 使用查詢路徑可能會導致從看似簡單的物件查詢對數據源執行複雜的命令。 這是因為需要一或多個聯結,才能在單一查詢中傳回相關物件。 這種複雜性在針對複雜實體模型的查詢中更大,例如具有繼承的實體,或包含多對多關聯性的路徑。
備註
使用 ToTraceString 方法查看將由 ObjectQuery<T> 產生的命令。 如需詳細資訊,請參閱 如何:檢視存放區命令。
當查詢路徑包含太多相關物件或物件包含太多數據列數據時,數據源可能無法完成查詢。 如果查詢需要超過數據源功能的中繼暫存記憶體,就會發生這種情況。 發生這種情況時,您可以藉由明確載入相關物件來減少數據源查詢的複雜性。
顯式載入相關物件
您可以在傳回 Load 或EntityCollection<TEntity>的導覽屬性上呼叫 EntityReference<TEntity> 方法,以明確載入相關物件。 明確載入物件時,每次呼叫Load都需要往返資料庫。
備註
如果您在迴圈遍歷傳回物件的集合時呼叫 Load ,例如當您在 Visual Basic 中使用 foreach 語句 (For Each) 時,數據源特定提供者必須在單一連接上支援多個作用中結果集。 使用 SQL Server 資料庫時,您必須在提供者連接字串中指定<值為 c0 />。
當實體上沒有 LoadProperty 或 EntityCollection<TEntity> 屬性時,您也可以使用 EntityReference<TEntity> 方法。 當您使用 POCO 實體時,這非常有用。
雖然明確載入相關物件會減少聯結數目,並減少多餘的數據量,但需要對資料庫進行重複連接, Load 這在明確載入大量物件時可能會變得昂貴。
儲存變更
當您在SaveChanges上呼叫ObjectContext方法時,會為內容中每個新增、更新或刪除的對象分別產生獨立的 create、update 或刪除命令。 這些命令會在單一交易中的數據源上執行。 如同查詢,建立、更新和刪除作業的效能取決於概念模型中對應的複雜性。
分散式交易
需要分散式交易協調器 (DTC) 所管理之資源的明確交易中的作業會比不需要 DTC 的類似作業更昂貴。 在下列情況下,會升級至 DTC:
針對 SQL Server 2000 資料庫或其他數據源進行作業的明確交易,一律會將明確交易升級至 DTC。
當 Entity Framework 管理連線時,涉及對 SQL Server 2005 作業進行的顯式交易。 這是因為每當連接在單一交易內關閉並重新開啟時,SQL Server 2005 就會升級為 DTC,這是 Entity Framework 的默認行為。 使用 SQL Server 2008 時,不會發生此 DTC 促銷活動。 為了避免在使用 SQL Server 2005 時出現此升級,您必須在交易中明確開啟和關閉連線。 如需詳細資訊,請參閱 管理連線和交易。
在一或多個作業在System.Transactions交易中執行時,會使用顯式交易。 如需詳細資訊,請參閱 管理連線和交易。
改善效能的策略
您可以使用下列策略來改善 Entity Framework 中查詢的整體效能。
預先產生檢視
第一次應用程式執行查詢時,根據實體模型產生檢視是相當重要的成本。 使用 EdmGen.exe 公用程式,將檢視預先產生為 Visual Basic 或 C# 程式代碼檔案,可在設計期間新增至專案。 您也可以使用文字範本轉換工具組來產生預先編譯的檢視。 預產生的視圖會在執行時進行驗證,以確保其與指定實體模型的當前版本一致。 如需詳細資訊,請參閱 如何:預先產生檢視以改善查詢效能。
使用非常大的模型時,適用下列考慮:
.NET 元數據格式會將指定二進位檔中的使用者字串字元數目限制為 16,777,215 (0xFFFFFF)。 如果您要為非常大的模型產生檢視,而檢視檔案達到此大小限制,您將會收到「沒有邏輯空間可建立更多使用者字串」編譯錯誤。 此大小限制適用於所有 Managed 二進位檔。 如需詳細資訊,請參閱示範如何使用大型和複雜模型時避免錯誤的 部落格 。
請考慮在查詢中使用 NoTracking 合併設定選項
追蹤對象內容中返回的物件需要成本。 為了監測物件的變更,並確保同一邏輯實體的不同請求返回相同的物件實例,必須將這些物件附加至 ObjectContext 實例。 如果您不打算對對物件進行更新或刪除,而且不需要身分識別管理,請考慮在執行查詢時使用 NoTracking 合併選項。
傳回正確的數據量
在某些情況下,使用 Include 方法指定查詢路徑會更快,因為它需要較少的往返資料庫。 不過,在其他情境中,額外的資料庫往返以載入相關物件的速度可能會更快,因為較少的聯結,使查詢較簡單,導致數據重複較少。 因此,我們建議您測試不同方法取得相關物件的效能表現。 如需詳細資訊,請參閱 載入相關物件。
若要避免在單一查詢中傳回太多數據,請考慮將查詢的結果分頁到更容易管理的群組中。 如需詳細資訊,請參閱 如何:逐頁查看查詢結果。
限制 ObjectContext 的範圍
在大部分情況下,您應該在ObjectContext語句中建立using實例(在 Visual Basic 中為Using…End Using)。 這可以藉由確保程式代碼結束語句區塊時,自動處置與對象內容相關聯的資源,以提升效能。 不過,當控件系結至對象內容所管理的物件時, ObjectContext 只要需要系結並手動處置,就應該維護 實例。 如需詳細資訊,請參閱 管理連線和交易。
請考慮手動開啟資料庫連線
當您的應用程式執行一系列對象查詢或經常呼叫 SaveChanges 來保存數據源的建立、更新和刪除作業時,Entity Framework 必須持續開啟並關閉與數據源的連接。 在這些情況下,請考慮在這些作業開始時手動開啟連線,並在作業完成時關閉或處置連線。 如需詳細資訊,請參閱 管理連線和交易。
效能資料
Entity Framework 的一些效能數據會在下列文章中發佈在 ADO.NET 小組部落格上: