2018 年 10 月

第 33 卷 10

本文章是由機器翻譯。

資料點-EF Core 中的 SQL 和變更追蹤的事件記錄

藉由Julie Lerman

Julie Lerman彈性的記錄 Api.NET Core 中的已開發一大福音。EF Core 會繫結到這些 Api,可讓您公開各種不同的記錄和偵錯即將從 EF Core 的資訊。

在本月的專欄中,我要示範一些此類型的資訊可以從 EF Core 執行階段取得幾種方式呈現該資訊,並如往常般,傳遞各種秘訣和技巧我在過程中了解。

不過,位於上.NET Core SDK,您會發現,多半的文件的任何項目是 ASP.NET Core 如何使用它使用記錄功能。請參閱,例如,官方的 Microsoft 文件中,有關在 ASP.NET Core 記錄bit.ly/2OiS4HD

您可以針對各種不同的目的地,感謝 ILoggerFactory,可以使用內建的提供者,例如 ConsoleLoggerProvider 和 DebugLoggerProvider,並甚至可以使用 Windows 事件、 AzureAppServices 和更多功能整合的輸出。提供者都會封裝在各種不同的擴充功能,例如 Microsoft.Extensions.Logging.Console。因為我的目標是要說明的資訊 EF Core 類型可以公開的不是如何整合各種不同的提供者,我絕對推薦查看上述的 ASP.NET Core 記錄文章,以及 Mark Michaelis 文章中,「 記錄與.NET Core「 在msdn.com/magazine/mt694089

在這裡,我只會使用提供者,輸出到主控台和偵錯工具。

記錄延伸模組需仰賴的。.NET Standard。這表示您可以使用在.NET Framework 4.6.1 和更高的應用程式或.NET Core 和 ASP.NET Core 中的記錄。ASP.NET Core 已內建的記錄,而且有感受到它的工作變得更容易許多。但是,我將使用的.NET Framework 應用程式,並設定記錄直接在 DbContext 類別中,為了示範呈現 EF Core 記錄,因為有使用來自 ASP.NET Core 記錄的已這麼多的範例。

EF Core 一般記錄

為了介面記錄資訊的 EF Core,您必須將繫結至 DbContext ILoggerFactory。然後,DbContext 就會與該登入程式 factory 中,來共用資訊。ASP.NET Core 會為您,但在 ASP.NET Core 之外此步驟必須明確地完成。

首先您必須定義 LoggerFactory 物件,這是您可以在其中指定 LoggerFactory 應該使用哪些提供者。假設我已經有 Microsoft.Extensions.Logging.Console 和使用的參考至該命名空間,我的 DbContext 類別中的陳述式,這是最簡單的方式來定義將會輸出 LoggerFactory 主控台視窗。

不同的提供者都有自己的建構函式定義。比方說,ConsoleProvider 有四個不同建構函式採用不同的物件,表示提供者的設定。其中一個這些建構函式會採用 lambda 運算式來呈現記錄層級和記錄類別。記錄層級協助組織不同類別的記錄詳細資料。一層是由包含一些基本資訊的記錄檔所組成。另一個保留的詳細記錄,以協助偵錯問題。記錄器與共用其資訊的 Api,判斷哪一個層級中屬於他們的資料。例如,EF Core 所呈現的資料庫命令被標記為 [資訊和偵錯的記錄層級。顯示由 EF Core 變更追蹤器詳細資料會標記為 [偵錯層級。我將示範其中一些範例。

類別取決於要共用的資訊類型。EF Core 2.1 會公開 12 種不同的記錄資訊。在 EF Core LoggerCategory 類別bit.ly/2KBLWs1是方便的參考,您會發現從它衍生的類別清單中所示**[圖 1**。

[圖 1 EF Core LoggerCategory 類別

ChangeTracking 移轉
資料庫 模型
Database.Command Model.Validation
Database.Connection Scaffolding
Database.Transaction 查詢
基礎結構 更新

請注意,那里記錄檔事件公開的移轉命令。

記錄層級和類別讓您輕鬆篩選為何 logger 輸出 — 我用它來顯示您剛才 SQL 和 ChangeTracker 詳細資料。

檢查 EF Core 產生的 SQL

記錄 SQL EF Core 活動所產生的命令可能是您會想要執行與 EF Core 最常見的記錄工作。當然,您一律可以選擇使用 SQL 程式碼剖析工具,例如 SQL Profiler for Windows,EF 獲 (efprof.com) 和 (處於預覽狀態) 程式碼剖析功能的跨平台的 SQL Operations Studio (bit.ly/2MnfN94)。但是,您可能永遠沒有存取這些工具,特別是如果您想要觸發從使用者的應用程式,不在開發人員電腦上的記錄檔。

因此,現在就開始建立產生傳送至資料庫的 SQL 登入程式 factory。SQL 命令是透過 Database.Command 類別公開。

我將建立名為 DbCommandConsoleLoggerFactory LoggerFactory,並接著說明程式碼:

public static readonly LoggerFactory DbCommandConsoleLoggerFactory
  = new LoggerFactory (new [] {
      new ConsoleLoggerProvider ((category, level) =>
        category == DbLoggerCategory.Database.Command.Name &&
        level == LogLevel.Information, true)
    });

請注意 LoggerFactory 是靜態。EF 小組 (和文件) 通知內容的每個執行個體使用相同的記錄器,應用程式存留期間。否則,您可能會遇到一些奇怪的副作用,也為慢速的 EF Core 的大幅處理。讓這個記錄器 factory 靜態可確保它會保留在範圍內,適用於整個應用程式的存留期內容的每個新執行個體。

登入程式 factory 可以包含一或多個提供者,可讓您同時也會跳出到多個目的地的記錄檔,但我將只新增一個。這個建構函式會採用不同的記錄器提供者的陣列。定義透過 new [] 陣列之後,我加入單一提供者 — ConsoleLoggerProvider。四個可以使用建構函式提供者,我將使用一個可讓我使用 lambda,由的分類和層級的記錄檔所組成的篩選。第一個參數是我將使用篩選述詞。我將提供變數的名稱-分類和層級 — 的運算式,然後建置所指出的分類 (字串) 應該等於 Database.Command 類別的名稱,且層級應該等於篩選值LogLevelInformation 列舉。

Database.Command 輸出 SQL 加上幾個其他 detailsthrough,偵錯層級,但只透過資訊層級的 SQL 命令。因此您最好指定其中一項 LogLevels 或您會收到重複的命令。偵錯會執行,以輸出命令,而資訊將輸出它們執行為。

第二個參數是布林值 IncludeScope 選項。記錄範圍可讓您將記錄檔,但就我所知,EF Core 不會使用它們。  

與 LoggerFactory,定義,它必須接著繫結至 DbContext,則可以在 OnConfiguring 方法。UseLoggerFactory 是 DbContextOptionsBuilder 方法。如果您定義其他選項,例如 UseSqlite,您可以附加 UseLoggerFactory。否則,您就可以直接從 optionsBuilder 呼叫。無論如何,您然後傳入您的類別中定義的處理站:

optionsBuilder.UseLoggerFactory(DbCommandConsoleLoggerFactory)

根據預設,EF Core 會保護您免於公開敏感性資料,例如篩選器的參數值。我要偵錯時我自己的程式碼,我通常會新增 EnableSensitiveDataLogging 方法,以公開:

optionsBuilder.UseLoggerFactory(
  DbCommandConsoleLoggerFactory).EnableSensitiveDataLogging();

現在設定來公開此資料的內容,我已建立一些資料,並將它儲存到我的內容會對應至資料庫的主控台應用程式。所有我很是建立單一的雜誌,並且將新的發行項新增至其發行項的集合:

using (var context = new PublicationsContext())
{
  var mag = new Magazine("MSDN Magazine", "1105 Media");
  var article = new Article("EF Core 2.1 Query Types","Julie Lerman");
  mag.Articles.Add(article);
  context.Magazines.Add(mag);
  var results=context.SaveChanges();
  Console.ForegroundColor = ConsoleColor.Red;
  Console.WriteLine($"Program Message: The app has stored {results} rows");
}

記錄器輸出三組資訊後面接著程式訊息輸出 (請參閱**[圖 2**)。請注意我混合記錄和主控台 WriteLine 訊息這裡特別針對這個簡單的示範。但您不應該執行此動作在實際執行的應用程式因為沒有機會處理衝突的記錄訊息沒有送達主控台。

在資訊層級 Database.Commands 篩選時,記錄輸出
在資訊層級 Database.Commands 篩選時,[圖 2 記錄輸出

來自記錄器的每個訊息開頭處會顯示一條線:

"info: Microsoft.EntityFrameworkCore.Database.Command[20101].
20101 is the unique EventId assigned to Executed DbCommand events.

第一行是 SQLite 傳送預設會針對每個新開啟的連接,以確保會遵守外部索引鍵的特殊命令。下一個命令會插入新的雜誌,並傳回其新產生的主索引鍵。最後一個命令的 MagazineId 外部索引鍵資料行中插入以及雜誌的索引鍵值的文件,並傳回其新產生的主索引鍵。最後,主控台應用程式自己的訊息,我指定 Console.WriteLine 輸出。

請注意資訊項目,說 「 執行 DbCommand,「 甚至轉送執行並將結果傳回至 EF Core,以繼續處理這些結果的命令所花費的時間。因為我啟用記錄的敏感性資料,您也可以查看傳入的參數值。

中所示,輸出如果我將 LogLevel 資訊變更為 [偵錯,會稍有不同, [圖 3。旗標為偵錯,而不是資訊。SQL 是仍然記錄,但為 「 執行 DbCommand 「 事件識別碼為 20100,相對於事件識別碼所在 20101 執行 DbCommand 事件。此外,Database.Command 轉送資料讀取器已處置之後接收新 PrimaryKeys 在 SELECT 陳述式的結果時的兩個其他事件。

從 LogLevel.Debug Database.Commands
[圖 3 Database.Commands 從 LogLevel.Debug

讓我們看看輸出相同的 Database.Commands DebugLoggerProvider。

首先,我將建立另一個 LoggerFactory,此時只會將推入 DebugLoggerProvider 透過記錄檔的其中一個。此提供者具有只有兩個建構函式,但其中一個是 ConsoleLoggerProvider 的建構函式類似,採用的篩選條件的 lambda 運算式,但如果沒有 IncludeScope 參數。我的新 LoggerFactory 看起來很接近其他:

public static readonly LoggerFactory DbCommandDebugLoggerFactory
  = new LoggerFactory (new [] {
      new DebugLoggerProvider(
        (category, level) => category == DbLoggerCategory.Database.Command.Name &&
                             level == LogLevel.Information)
      );

在修改之後使用此物件 optionsBuilder.UseLoggerFactory,主控台會顯示程式訊息,並按下任何索引鍵的輸出。

但是,如果我將顯示偵錯輸出的 [Visual Studio [輸出] 視窗時,我可以看到 Visual Studio 偵錯訊息之間記錄器的 SQL 命令輸出。[圖 4顯示一些此輸出。請注意,EF Core 記錄 「 資訊 」 旗標或事件識別碼,不需要但待辦事項清單後面的記錄層級名稱的類別目錄。

Database.Commands 寫入至偵錯] 視窗外
[圖 4 Database.Commands 寫出至偵錯] 視窗

現在讓我們看看的變更追蹤分類。EF Core 變更追蹤器運作的方式是沒什麼興趣,我敢說很棒吧?我希望貴用戶同意。此類別中的事件可分為偵錯記錄層級。

我要建立另一個 LoggerFactory 同樣地,使用 ConsoleLoggerProvider,但這次我的篩選器結合 Database.Commands 和變更追蹤的事件。藉由偵錯層級篩選,我可以取得不只是所有的變更追蹤的事件,但也命令所擷取的 SQL。請記住,偵錯引發執行 DbCommand 事件和其他一些:

public static readonly LoggerFactory ChangeTrackingAndSqlConsoleLoggerFactory
  = new LoggerFactory(new[] {
      new ConsoleLoggerProvider (
        (category, level) =>
        (category == DbLoggerCategory.ChangeTracking.Name |
        category==DbLoggerCategory.Database.Command.Name)
        && level==LogLevel.Debug ,true)
  });

切換之後若要使用此登入程式 factory optionsBuilder,輸出是很不一樣 !該輸出的第一個部分所示**[圖 5**。如果您不熟悉如何使用變更追蹤程式如何達到其業務,這可能是您特別有趣。請注意,第二個偵錯登入**[圖 5**切換內容名稱使用實體名稱 — 一個已知的 bug 誰的修正程式會出現在 EF Core 2.2。

[圖 5 的第一個部分的變更追蹤和 Database.Command 類別事件

dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10808]
      'PublicationsContext' generated temporary value '-2147482647' for
      the 'MagazineId' property of new 'Magazine' entity.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'Magazine' started tracking '{MagazineId: -2147482647}'
      entity with key 'PublicationsContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10808]
      'PublicationsContext' generated temporary value '-2147482647' for
      the 'ArticleId' property of new 'Article' entity.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10806]
      Context 'Article' started tracking '{ArticleId: -2147482647}'
      entity with key PublicationsContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10800]
      DetectChanges starting for 'PublicationsContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10801]
      DetectChanges completed for 'PublicationsContext'.

為之前,每一個項目是前面加上的分類,並提供相關的事件識別碼。當我的方法呼叫的內容時,就會開始事件。Magazines.Add。

第一個事件是產生暫存索引鍵值 (EventId 10808) 新的雜誌的內容,然後開始追蹤的索引鍵 (EventId 10806)。接下來,內容會針對新發行項重複相同的兩個步驟 (介於 10808 和 10806 一次)。暫存的索引鍵值是相同因為它們是不同的實體類型和-2147482647 是暫存的索引鍵值的起點。

加入之後本雜誌以及其文件內容,接著我呼叫 SaveChanges。這會觸發 DetectChanges,您可以看到開始和結束時間的事件。沒有任何變更的實體會被追蹤的時間,因為要探索,因此這後面緊接跟著"DetectChanges 完成 」。 請注意,因為這些是新的實體,即使項目已變更,其狀態會仍被加入。

正在偵測變更的內容完成之後,它會開始傳送至資料庫的 SQL。因此接下來記錄檔中的外部索引鍵的值設定,後面接著插入雜誌,並傳回其新 MagazineId 命令的特殊 SQLite 命令。

然後變更追蹤程式就會執行一次,取代以新的資料庫產生值 MagazineId 資料庫所產生的暫存值 — 第一次 Magazine.MagazineId 然後再 Article.MagazineId 修正:

dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10803]
      Foreign key property 'Magazine.MagazineId' detected as changed from
      '-2147482647' to '17' for entity with key '{MagazineId: 17}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10803]
      Foreign key property 'Article.MagazineId' detected as changed from
      '-2147482647' to '17' for entity with key '{ArticleId:
      -2147482647}'.

現在文章 MagazineId 具備正確的外部索引鍵值,而且下一個記錄檔事件會傳送 SQL 來插入資料庫中的發行項的 EF Core。

最後,兩個其他的變更追蹤事件會記錄為 EF Core 修訂本雜誌以及它正在追蹤未變更的發行項的已知的狀態:

dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Magazine' entity with key '{MagazineId: 17}' tracked by
      'PublicationsContext' changed from 'Added' to 'Unchanged'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Article' entity with key '{ArticleId: 17}' tracked by
      'PublicationsContext' changed from 'Added' to 'Unchanged'.

我喜歡看到這當然配置。我使用的 Entity Framework 十年來,我花了很多時間執行程式碼偵錯和很多心力挖出執行階段偵錯工具,並監看式視窗,以了解變更追蹤程式的運作方式。

變更追蹤詳細資料,特別是當您修改並儲存它,來自資料庫的資料互動是真的令人印象深刻。讓我們看看它使用新的方法時,在 GetAndUpdateSomeData [圖 6,表示擷取的雜誌,內含一個發行項,編輯,然後將儲存所做的變更。因此我可以在記錄檔中看到我進行編輯,而且當我呼叫 SaveChanges 時執行查詢,方法也寫出通知 ("的查詢,"[編輯] 和 [儲存])。

[圖 6] 新的方法,來瀏覽記錄

private static void GetAndUpdateSomeData()
{
  using (var context = new PublicationsContext())
  {
    Console.WriteLine("Query");
    var mag = context.Magazines.Include(m=>m.Articles)
      .FirstOrDefault(m => m.MagazineId == 1);
    Console.WriteLine("Edit");
    mag.Articles.FirstOrDefault().AuthorName += " and Friends";
    Console.WriteLine("Save");
    var results = context.SaveChanges();
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"Program Message: The app has updated {results} rows");
  }

我不會列出整個記錄檔,但它會先顯示 SQL 來查詢雜誌 MagazineId 1。記錄檔做為提醒您,在 EF Core,有些包括 (值得注意的是適用於集合) 現在分成個別的查詢,在資料庫上因為它是相對於結果的大小和事半功倍,無須具體化物件通常更有效率。雜誌會傳回,另一個事件之後,內容開始追蹤該雜誌,會顯示。接下來,EF Core 會傳送 SQL 來擷取所有我篩選,這會傳回只有一個發行項,因為這是我的所有資料雜誌文章 (使用 MagazineId 只有一個 = 1)。下一個事件是內容已開始追蹤該發行項的實體。在此之後,有兩個事件,指出資料讀取器已處置,分別用於兩個查詢。

如您在中所見,事情就有趣多了,現在**[圖 7**,這會顯示記錄檔的其餘部分。

[圖 7 更新期間的變更追蹤記錄檔

Edit
Save
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10800]
      DetectChanges starting for 'PublicationsContext'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10802]
      Unchanged 'Article.AuthorName' detected as changed from 'Julie Lerman' to
     'Julie Lerman and Friends' and will be marked as modified
      for entity with key '{ArticleId: 1}'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Article' entity with key '{ArticleId: 1}' tracked by
      'PublicationsContext' changed from 'Unchanged' to 'Modified'.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10801]
      DetectChanges completed for 'PublicationsContext'.
dbug: Microsoft.EntityFrameworkCore.Database.Command[20100]
      Executing DbCommand [Parameters=[], CommandType='Text',
      CommandTimeout='30'] PRAGMA foreign_keys=ON;
dbug: Microsoft.EntityFrameworkCore.Database.Command[20100]
      Executing DbCommand [Parameters=[@p1='1' (DbType = String),
      @p0='Julie Lerman and Friends' (Size = 24)], CommandType='Text',
      CommandTimeout='30']
      UPDATE "Article" SET "AuthorName" = @p0
      WHERE "ArticleId" = @p1;
      SELECT changes();
dbug: Microsoft.EntityFrameworkCore.Database.Command[20300]
      A data reader was disposed.
dbug: Microsoft.EntityFrameworkCore.ChangeTracking[10807]
      The 'Article' entity with key '{ArticleId: 1}' tracked by
      'PublicationsContext' changed from 'Modified' to 'Unchanged'.

首先要請您注意的是我的 「 編輯 」 訊息會緊接著 [儲存] 訊息。即使內容目前正在追蹤我正在編輯的文件,它會有無法更新即時狀態資訊。它會等到呼叫變更追蹤程式 DetectChanges 方法時,明確地在程式碼或其他的 EF Core 方法,例如 SaveChanges。如此就不直到之後呼叫 SaveChanges 該 DetectChanges 會開始其工作。它會看到第一件事是 AuthorName 已變更。18082 事件轉送該資訊,並指出,這項變更,因為它計劃來標記實體 (發行項的發行項識別碼為 1) 為已修改。下一個事件,也就是 18087,狀態就會變更該實體 (發行項識別碼:1) 從未變更為已修改。這是令人讚嘆的詳細資料,因此當您偵錯時有任何問題或非預期的行為,它非常有幫助。我很感謝 !(EF 小組,以記錄 Arthur Vickers hat 提示是他 baby) !

變更追蹤程式完成這項工作之後,接著會看到更新方法。同樣地,我已經啟用機密資料的事實是為什麼您可以看到所有的 SQL 命令的參數。

[選取的變更] 結尾的 SQL 是 SQLite 的方式傳回受影響的資料列計數。

最後,變更追蹤清除追蹤物件,將文章標示為未變更,並且可以用於其下一步] adventure 的狀態。

別忘了其他的記錄器類別

看來我必須填寫這份雜誌,為各位示範其他 10 DbLoggerCategory 類型 EF Core 公開的優缺點。您現在已在控點如何公開資料,您可以試驗不同的類別,並查看它們所共用的資訊種類。或者,您可以只讓 EF Core 跳出的任何項目定義不會篩選的記錄提供者的所有記錄資料。而不是篩選述詞運算式,只會傳回 true 為 「 我已完成此太多資訊 (TMI) 處理站中:

public static readonly LoggerFactory TMIConsoleLoggerFactory
  = new LoggerFactory(new[] {
      new ConsoleLoggerProvider ((category, level) => true, false)
  });

雖然記錄是非常有幫助針對問題進行偵錯,我認為同樣重要的洞察 API 的運作方式。和您愈了解其運作方式,比較不可能是您將建立要進行偵錯的問題。

Julie LermanMicrosoft 地區主管、 Microsoft MVP、 軟體小組指導,以及位於 Vermont 山區顧問。您可以找到其呈現在資料存取和使用者群組和所做的心得世界各地的其他主題。在她部落格thedatafarm.com/blog和以及 Code First DbContext 版本中的,所有從 O'Reilly Media 是"程式設計 Entity Framework"的作者。在 Twitter 上關注她: @julielerman ,請參閱在她 Pluralsight 課程 juliel.me/PS 影片。

感謝下列 Microsoft 技術專家來檢閱這篇文章:Arthur Vickers


MSDN Magazine 論壇中的這篇文章的討論