ADO.NETの処理を診断するAdoNetProfiler

この記事はC# Advent Calendar 2016の3日目の記事です。

今回は1年くらい前に作ったライブラリについて書いていきます。(記事を書くにあたって1年近くアップデートしていなかったものを急遽アップデート入れたのは秘密です。)

## AdoNetProfiler

### AdoNetProfilerとは

ADO.NETのDBに対して通信が発生する各処理で診断を行えるようにしたライブラリです。

パッケージはNuGetに公開済みでコードはGithub上で公開しています。

* NuGet - <a href="https://www.nuget.org/packages/AdoNetProfiler/">AdoNetProfiler</a>
* Github - <a href="https://github.com/ttakahari/AdoNetProfiler">ttakahari/AdoNetProfiler</a>

System.Data.Common名前空間を各クラスをラップした作りになっています。

現在対応しているターゲットのフレームワークは以下の通りです。

* .NET Framework 4.6 以降
* .NET Standard 1.6 以降

### それMiniProfiler?

似たようなライブラリにMiniProfilerというものがあります。

* Project Site - <a href="http://miniprofiler.com/">MiniProfiler</a>
* NuGet - <a href="https://www.nuget.org/packages/MiniProfiler/">MiniProfiler</a>
* Github - <a href="https://github.com/MiniProfiler/dotnet/">MiniProfiler/dotnet</a>

出来ることはほぼ一緒ですが、MiniProfilerがフックしているのはSQL実行の部分のみで、他の通信部分、例えばコネクションのOpen / Closeやトランザクションの開始 / 終了にフックすることは出来ませんでした。

これらの処理にフックすることが出来れば各処理にかかった時間だけでなく、コネクションを繋ぎっぱなしにしていた時間やトランザクションを張っていた時間まで知ることが出来ます。

この辺に不満があったためMiniProfilerを使わずに新しくAdoNetProfilerを作ることにしました。

あとMiniProfilerは一応まだ開発されているようですが、.NET Coreの対応は進んでいないようです。

## 使い方

先ほども書きましたがパッケージをNuGetにアップロードしているのでNuGetから落として使うことが出来ます。

```
PM > Install-Package AdoNetProfiler
```

### その1

DbConnectionを継承したコネクションクラスのオブジェクトと、後で説明しますがIADoNetProfilerというインターフェースを実装したオブジェクトを、DbConnectionクラスのラップであるAdoNetProfilerConnectionというクラスのコンストラクタに渡せば、これまで通りのDBアクセス処理のロジックを書くことが出来ます。

```csharp
var sqlConnection = new SqlConnection("[接続文字列]"); // コネクションクラスのオブジェクト
var traceProfiler = new TraceProfler();               // IAdoNetProfilerを実装したクラスのオブジェクト

using (var connection = new AdoNetProfiler(sqlConnection, traceProfiler))
{
    // DBアクセス処理
}
```

### その2

もう一つの方法はDbProviderFactoryからコネクションを生成する使い方です。

アプリケーションの起動時または初期化時に、AdoNetProfilerFactoryのInitializeメソッドにIAdoNetProfilerを実装したクラスのTypeを渡せば、DbProviderFactoriesからDbProviderFactoryを取得して、そこからコネクションクラスを生成すればAdoNetProfilerConnectionのインスタンスが生成されます。

```csharp
// アプリケーションの起動時または初期化時に呼び出す
AdoNetProfilerFactory.Initialize(typeof(TraceProfiler));

// これまで通りDbProviderFactoryを取得
var factory = DbproviderFactories.GetFactory("System.Data.SqlClient");

// AdoNetProfilerConnectionが生成される
using (var connection = factory.CreateConnection())
{
    connection.ConnectionString = "[接続文字列]";

    // DBアクセス処理
}
```

ただしこの方法は .NET Framework をターゲットとしたアプリケーションでしか使うことが出来ません。(DbProviderFactoriesが .NET Core に存在しないためです。)

### IAdoNetProfiler

フックした時の処理を定義したインターフェースがIAdoNetProfilerです。

このインターフェースを実装して、ADO.NETの各処理に挟みたい処理を書くことが出来ます。

IAdoNetProfilerが用意しているメソッドは以下の通りです。(ドキュメントコメント入れると長くなるので省略します。)

```csharp
public interface IAdoNetProfiler
{
    bool IsEnabled { get; }

    void OnOpening(DbConnection connection);

    void OnOpened(DbConnection connection);

    void OnClosing(DbConnection connection);

    void OnClosed(DbConnection connection);

    void OnStartingTransaction(DbConnection connection);

    void OnStartedTransaction(DbTransaction transaction);

    void OnCommitting(DbTransaction transaction);

    void OnCommitted(DbConnection connection);

    void OnRollbacking(DbTransaction transaction);

    void OnRollbacked(DbConnection connection);

    void OnExecuteReaderStart(DbCommand command);

    void OnReaderFinish(DbDataReader reader, int record);

    void OnExecuteNonQueryStart(DbCommand command);

    void OnExecuteNonQueryFinish(DbCommand command, int executionRestlt);

    void OnExecuteScalarStart(DbCommand command);

    void OnExecuteScalarFinish(DbCommand command, object executionRestlt);

    void OnCommandError(DbCommand command, Exception exception);
}
```

見てわかる通り非常に多いです。

というのも基本的にフックする各処理の前後分、メソッドを用意しているためです。

フックポイントは以下になってます。

* DbConnection - Open / Close / BeginTransaction
* DbTransaction - Commit / Rollback
* DbCommand - ExecuteReader (処理前のみ) / ExecuteNonQuery / ExecuteScalar
* DbDataReader - Close (またはDispose)

各処理をデリゲートで渡して内部で呼び出してもらうようにする、というインターフェースも一応考えてはいますが呼び出されなかったり(ここはRoslyn Analyzerでどうにかなりそう)、DataReaderの読み込み処理が前と後で別々のクラスのメソッドになっていてデリゲートにしにくい、という問題があってなかなか進んでいない状況です。

## Glimpse対応

### Glimpseとは

割と有名なのでご存知の方も多いと思いますが、ASP.NET専用のプロファイリングツールです。

* Project Site - <a href="http://getglimpse.com/">Glimpse</a>
* NuGet - <a href="https://www.nuget.org/packages/Glimpse/1.8.6">Glimpse.Core</a>
* Github - <a href="https://github.com/Glimpse/Glimpse">Glimpse/Glimpse</a>

Webアプリケーションの実行中に各処理をタイムラインで、詳細情報をタブで表示することが出来ます。(以下はタイムラインの例です。)

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/ddd8e5f5-81b4-e73a-812e-7a8127b81d66.png)

開発チームがマイクロソフトに吸収されてASP.NET Coreへの対応の期待も高まったのですが、現状開発は止まっているようです。

### Glimpse.AdoNetProfiler

Glimpseは独自のプラグインを作成することが出来るのですが、AdoNetProfilerも対応プラグインを作成してあります。

NuGet経由でインストール出来ます。

```
PM > Install-Package Glimpse.AdoNetProfiler
```

元々Glimpse.Adoというプラグインが本家で開発されていたのですが、表示される情報が少ないことからAdoNetProfilerでも対応することにしました。

タイムラインでは各DBアクセスの処理だけでなく、コネクションの接続期間やトランザクションの発行期間が見れるようになっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/0bffd725-4dff-9de4-59eb-1f2fa7d75922.png)

拡張タブは Connection / Transaction / Command をデフォルトで用意しており、各々ドリルダウンでより詳細を表示していくイメージになります。

Connectionタブではコネクション単位で実行されたSQLやトランザクションの情報を見ることができ、同一の接続先に対して複数回接続されたり、同一ステートメントのSQLを複数回検知すると警告が出るようにもなっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/177c206c-2cd3-6010-9242-c60633d9b4b5.png)

Transactionタブではトランザクション発行中に実行されたクエリが見れるようになっています。

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/c50e1519-974d-fad6-661f-3b0d203b824a.png)

Commandタブでは実行されたクエリのパラメータや取得・更新件数など詳細な情報を見ることが出来ます。

![image](https://qiita-image-store.s3.amazonaws.com/0/39403/43494db8-5d11-e58e-fe23-03d2e74f8b34.png)

また、表示している情報の格納しているクラスのスコープはpublicになっているので、これらの情報をもとにさらに独自のタブを表示することも可能です。

## まとめ

DBアクセスに限らず外部I/Oのアクセスはボトルネックになりやすいので何らかの形で診断できるようにしておいた方が良いと思います。

どんな形であれ.NETの標準として何か用意はして欲しいですね。

今後のアップデート予定ですが、とりあえずIAdoNetProfilerのインターフェースをもっと使いやすい形にはしたいですね。

あと.NET CoreでADO.NETの内部処理が一部変わっている箇所があるようなのでもしかしたらバグなんかもあるかもしれません。
その時は各SNSで連絡していただくか、GithubでIssuesを上げてもらえると幸いです。