Entity Framework の SQL ログ出力を行う【EFProviderWrappers編】

Entity Framework は残念な事に DBアクセス時のSQLを出力するオプションがありません。実際にどんなSQLが実行されているか把握できるようにしておかないと大抵困ります。ログ出力を行うためのラッパーが作成されているためのそれを利用します。

環境

手順

前提

既にEntity Framework を利用したASP.NET MVC アプリケーション(以下アプリケーション)が存在するとします。

EFProviderWrappers のダウンロードとビルド

  1. Tracing and Caching Provider Wrappers for Entity Framework in C# for Visual Studio 2010からソースをダウンロードします。
  2. ダウンロードした「Tracing and Caching Provider Wrappers for Entity Framework.zip」を解凍します。
  3. 「解凍先\C#\EFProviderWrappers.sln」をVisualStudio2010で開き、構成「Release」でビルドします。
  4. 「解答先\C#\EFProviderWrapperDemo\bin\Release」にある、EFProviderWrapperToolkit.dll, EFTracingProvider.dll をコピーします。

参照の追加

コピーしたDLLへの参照をアプリケーションに追加します。

拡張DbContextの作成

  1. ダウンロードしたソリューションに含まれている EFProviderWrapperDemo プロジェクトから ExtendedNorthwindEntities.cs をアプリケーションにコピーします。今回はファイル名、クラス名を NorthwindPhotoShare に置換してます。
  2. このクラスは DbContext を継承しているので*1継承元を NorthwindEFEntities からログ出力するDbContextObjectContextを継承しているクラス*2に変更します。このDbContext の名前が PhotoShareEntities の場合は、ExtendedPhotoShareEntities.cs は以下のようなソースになります。なお、DBへの接続文字列はPhotoShareEntitiesです。
public partial class ExtendedPhotoShareEntities : PhotoShareEntities
{
    private TextWriter logOutput;

    public ExtendedPhotoShareEntities()
        : this("name=PhotoShareEntities")
    {
    }

    public ExtendedPhotoShareEntities(string connectionString)
        : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                connectionString,
                "EFTracingProvider"
        ))
    {
    }
}

Web.Configファイルへの登録

アプリケーションの Web.Config へ設定を追加します。

  <system.data>
    <DbProviderFactories>
      <add name="EF Tracing Data Provider" invariant="EFTracingProvider" description="Tracing Provider Wrapper" type="EFTracingProvider.EFTracingProviderFactory, EFTracingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
    </DbProviderFactories>
  </system.data>

拡張DbContext生成メソッド、および、ログ出力メソッドの追加

ExtendedPhotoShareEntities に、CreateExtendedPhotoShareEntities というメソッドが存在します。
これを以下のように実装します。

public static ExtendPhotoShareEntities CreateExtendPhotoShareEntities()
{
    ExtendPhotoShareEntities context = new ExtendPhotoShareEntities();

    context.CommandFinished += SqlCommandFinished;

    return context;
}

private static void SqlCommandFinished(object sender, CommandExecutionEventArgs e)
{
    Logger _logger = LogManager.GetLogger("SQL");

    string sqlStr = e.Command.CommandText;

    // パラメータ名の長い順にソート
    var SortedParameters = (from p in e.Command.Parameters.Cast<DbParameter>() orderby p.ParameterName.Length descending select p );

    foreach (var parameter in SortedParameters)
    {
        sqlStr = sqlStr.Replace(parameter.ParameterName,
                    ToDbFormattedString(parameter.Value));
    }
    sqlStr = sqlStr.Replace("\r\n", " ");
    _logger.Info(sqlStr);
}

private static string ToDbFormattedString(object p)
{
    if (p is DateTime)
        return " TO_DATE('" + ((DateTime)p).ToString() + "','YYYY/MM/DD HH24:MI:SS')";
    if (p is String)
        return "'" + (p as string).Replace("'", "''") + "'";
    return p.ToString();
}

あとは DbContextのインスタンス作成を以下のように実装します。

void insert(Photo target) 
{
    using (ExtendedPhotoShareModel context = ExtendedPhotoShareEntities.CreateExtendedPhotoShareModel())
    {
        // ここにDBアクセスのを記述。
        context.Photos.Add(target);
        context.SaveChanges();
    }
}

備考

上記の置換ロジックを見るとわかりますが Oracle用のSQL、パラメータを置換してます。
つまり、ODP.NET でも出力できました。SQLServerのサンプルを見たい場合は こちらのサイト(Logging all SQL statements done by Entity Framework)やDemoプロジェクトを参考にしてください。とパラメータの形式が異なるのでログ出力メソッドを変更する必要があります。

注意

ExecuteStoreQuery について

Q and A - Tracing and Caching Provider Wrappers for Entity Framework in C# for Visual Studio 2010でも議論されていることですが、
Entity Framework で素のSQL、を発行する時はExecuteStoreQueryを使うと思います。このWrapperを利用してExecuteStoreQueryを実行すると、NoSupportedExceptionが発生します。ソース見ればわかりますが、
「DbConnectionWrapper.cs」の以下の箇所が原因です。

protected override DbCommand CreateDbCommand() 
{ 
    throw new NotSupportedException(); 
} 

ここを以下のように直せば呼び出す事はできますが、ログ出力のために発生させるイベント CommandFinished が発生しませんので注意。

protected override DbCommand CreateDbCommand() 
{ 
    return this.wrappedConnection.CreateCommand();
} 

最後に

ここまで書いておいてなんですが、VisualStudio2012形式のソリューションになっていないことや、自分で拡張したいという思いから、次回は自分でWrapperを作る予定です。なのでタイトルは【EFProviderWrappers編】となっています。サンプルが出来たらエントリを書きます。

*1:2012/11/19変更 NorthwindEFEntities はObjectContext を継承していたためDbContext を継承していると記載した箇所を削除

*2:2012/11/20変更 ObjectContextを継承したクラスを利用する必要があります。