EF Core での診断リスナーの使用

ヒント

この記事のサンプルは GitHub からダウンロードできます。

診断リスナーを使用すると、現在の .NET プロセスで発生する任意の EF Core イベントをリッスンできます。 DiagnosticListener クラスは、実行中のアプリケーションから診断情報を取得するための .NET 全体の共通メカニズムの一部です。

診断リスナーは、単一の DbContext インスタンスからイベントを取得するのには適していません。 EF Core インターセプターは、コンテキストごとの登録によって同じイベントへのアクセスを可能にします。

診断リスナーは、ログ記録向けに設計されていません。 ログには、シンプルなログまたは Microsoft.Extensions.Logging の使用を検討してください。

例: 診断イベントの監視

EF Core イベントの解決は、2 つのステップから成るプロセスです。 まず、DiagnosticListener 自体のオブザーバーを作成する必要があります。

public class DiagnosticObserver : IObserver<DiagnosticListener>
{
    public void OnCompleted()
        => throw new NotImplementedException();

    public void OnError(Exception error)
        => throw new NotImplementedException();

    public void OnNext(DiagnosticListener value)
    {
        if (value.Name == DbLoggerCategory.Name) // "Microsoft.EntityFrameworkCore"
        {
            value.Subscribe(new KeyValueObserver());
        }
    }
}

OnNext メソッドは EF Core から取得した DiagnosticListener を検索します。 このリスナーには "Microsoft.EntityFrameworkCore" という名前が付いており、これは示されているように DbLoggerCategory クラスから取得できます。

このオブザーバーは、アプリケーションの Main メソッドなど、グローバルに登録する必要があります。

DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());

次に、EF Core DiagnosticListener が見つかると、実際の EF Core イベントをサブスクライブするために新しいキー値オブザーバーが作成されます。 次に例を示します。

public class KeyValueObserver : IObserver<KeyValuePair<string, object>>
{
    public void OnCompleted()
        => throw new NotImplementedException();

    public void OnError(Exception error)
        => throw new NotImplementedException();

    public void OnNext(KeyValuePair<string, object> value)
    {
        if (value.Key == CoreEventId.ContextInitialized.Name)
        {
            var payload = (ContextInitializedEventData)value.Value;
            Console.WriteLine($"EF is initializing {payload.Context.GetType().Name} ");
        }

        if (value.Key == RelationalEventId.ConnectionOpening.Name)
        {
            var payload = (ConnectionEventData)value.Value;
            Console.WriteLine($"EF is opening a connection to {payload.Connection.ConnectionString} ");
        }
    }
}

OnNext メソッドは今回、EF Core イベントごとにキーと値のペアを指定して呼び出されます。 キーはイベントの名前で、次のいずれかから取得できます。

  • すべての EF Core データベース プロバイダーに共通のイベントの CoreEventId
  • すべてのリレーショナル データベース プロバイダーに共通のイベントの RelationalEventId
  • 現在のデータベース プロバイダーに固有のイベントの類似クラス。 たとえば、SQL Server プロバイダーの場合は SqlServerEventId です。

キー/値ペアの値は、イベントに固有のペイロードの型です。 想定するペイロードの型は、これらのイベント クラスで定義されている各イベントに記載されています。

たとえば、上記のコードは、ContextInitialized イベントと ConnectionOpening イベントを処理します。 1 つ目の場合、ペイロードは ContextInitializedEventData です。 2 つ目の場合は、ConnectionEventData です。

ヒント

すべての EF Core イベント データ クラスで ToString をオーバーライドすると、イベントに対して同等のログ メッセージが生成されます。 たとえば、ContextInitializedEventData.ToString を呼び出すと、"Entity Framework Core 5.0.0 では、プロバイダー 'Microsoft.EntityFrameworkCore.Sqlite' (オプション: None を指定) を使用して 'BlogsContext' を初期化しました" が生成されます。

サンプルには、Blogging データベースに変更を加える単純なコンソール アプリケーションが含まれていて、発生した診断イベントが出力されます。

public static void Main()
{
    DiagnosticListener.AllListeners.Subscribe(new DiagnosticObserver());

    using (var context = new BlogsContext())
    {
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        context.Add(
            new Blog { Name = "EF Blog", Posts = { new Post { Title = "EF Core 3.1!" }, new Post { Title = "EF Core 5.0!" } } });

        context.SaveChanges();
    }

    using (var context = new BlogsContext())
    {
        var blog = context.Blogs.Include(e => e.Posts).Single();

        blog.Name = "EF Core Blog";
        context.Remove(blog.Posts.First());
        blog.Posts.Add(new Post { Title = "EF Core 6.0!" });

        context.SaveChanges();
    }

このコードからの出力は、検出されたイベントを示しています。

EF is initializing BlogsContext
EF is opening a connection to Data Source=blogs.db;Mode=ReadOnly
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to Data Source=blogs.db;Mode=ReadOnly
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db
EF is initializing BlogsContext
EF is opening a connection to DataSource=blogs.db
EF is opening a connection to DataSource=blogs.db