2013年10月30日 星期三

初探 Entity Framework 6 - Logging

我曾經寫過這麼一篇文章「ASP.NET MVC + NLog + Clutch.Diagnostics.EntityFramework 追蹤 EF 執行的 SQL Command」介紹可以使用 NLog 與 Clutch.Diagnostics.EntityFramework 在偵錯模式下可以將 EF 所產生並執行的 SQL Command 給顯示在 Visual Studio 的 Output 視窗中,如果網站放到測試機或是正式機要查看系統裡 EF 所執行的 SQL Command 還是可以透過 MiniProfilerGlimpse,這兩個工具我之前也介紹過。

Entity Framework 6 提供了一個新功能「Logging」,這個功能可以取代 NLog + Clutch.Diagnostics.EntityFramework,不過這只有 Entity Framework 6 才有提供,使用 EF 5 之前版本則是沒有這個功能,以下就說明怎麼使用這個功能。

 


參考文件:

MSDN – Data Developer Center > Entity Framework > Logging and Intercepting Database Operations (EF6 Onwards)

 

這邊繼續使用上一篇「初探 Entity Framework 6 的 Async/Await 功能」所建立的網站來做操作,依照 EF6 教學文件的說明,我們假如要使用 EF6 的 Logging 功能,可用以下的方式,

image

但我們所執行的是網站而不是 Console 專案,所以還是希望可以將 Log 給輸出到 Visual Studio 的 Output 視窗中,所以可以改成以下的方式,程式裡所使用的 Debug,其命名空間為「System.Diagnostics」,

image

執行結果

image

可以看到 EF 所執行的 SQL Command、執行開始時間以及執行所需時間。

image

上面這個 Log 的內容還可看到執行 SQL Command 的 Parameter 名稱、資料、類別、大小等。

 

如果 Controller 裡並不是直接對 DbContext 做資料存取而是透過 Repository 呢?我這邊的作法則是在 GenericRepository 的建構式內加上程式碼,這樣就不需要一個一個去手動加入程式碼。

image

 

在教學文件上面有看到 Log 的內容格式是可以讓我們自己設定,依照文件的說明,要建立一個繼承 DatabaseLogFormatter 的 OneLineFormatter,

image

OneLineFormatter 內容

public class OneLineFormatter : DatabaseLogFormatter
{
    public OneLineFormatter(DbContext context, Action<string> writeAction)
        : base(context, writeAction)
    {
    }
 
    public override void LogCommand<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
        Write(string.Format(
            "Context '{0}' is executing command '{1}'{2}",
            Context.GetType().Name,
            command.CommandText.Replace(Environment.NewLine, ""),
            Environment.NewLine));
    }
 
    public override void LogResult<TResult>(
        DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
    {
    }
}

接著就是要建立一個 MyDbConfiguration.cs 檔案,

image

MyDbConfiguration 程式內容

public class MyDbConfiguration : DbConfiguration
{
    public MyDbConfiguration()
    {
        SetDatabaseLogFormatter(
            (context, writeAction) => new OneLineFormatter(context, writeAction));
    }
}

只要建立好 OneLineFormatter 與 MyDbConfiguration 這兩個檔案就可以了,不必再更動其他的程式設定,就可以改變 EF 的 Log 格式,

image

以上是直接取用 Entity Framework 教學文件裡的程式內容,如果各位真的需要自訂一個 Log Formatter 的話,可以參考 Entity Frameowork 的原始碼,現在 Entity Framework 已經開放原始碼了,所以可以到以下的網址下載,

http://entityframework.codeplex.com

可以參考「DatabaseLogFormatter.cs」這個檔案的內容,尤其是需要好好地看「LogCommand<TResult>, LogParameter<TResult>, LogResult<TResult>」這幾個方法的內容,我這邊就不再另外說明,我是覺得預設的 LogFormatter 就已經可以滿足我查看資料的需求。

image

/// <summary>
/// Called to log a command that is about to be executed. Override this method to change how the
/// command is logged to <see cref="WriteAction" />.
/// </summary>
/// <typeparam name="TResult">The type of the operation's results.</typeparam>
/// <param name="command">The command to be logged.</param>
/// <param name="interceptionContext">Contextual information associated with the command.</param>
public virtual void LogCommand<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
    Check.NotNull(command, "command");
    Check.NotNull(interceptionContext, "interceptionContext");
 
    var commandText = command.CommandText ?? "<null>";
    if (commandText.EndsWith(Environment.NewLine, StringComparison.Ordinal))
    {
        Write(commandText);
    }
    else
    {
        Write(commandText);
        Write(Environment.NewLine);
    }
 
    if (command.Parameters != null)
    {
        foreach (var parameter in command.Parameters.OfType<DbParameter>())
        {
            LogParameter(command, interceptionContext, parameter);
        }
    }
 
    Write(interceptionContext.IsAsync
              ? Strings.CommandLogAsync(DateTimeOffset.Now, Environment.NewLine)
              : Strings.CommandLogNonAsync(DateTimeOffset.Now, Environment.NewLine));
}
 
/// <summary>
/// Called by <see cref="LogCommand" /> to log each parameter. This method can be called from an overridden
/// implementation of <see cref="LogCommand" /> to log parameters, and/or can be overridden to
/// change the way that parameters are logged to <see cref="WriteAction" />.
/// </summary>
/// <typeparam name="TResult">The type of the operation's results.</typeparam>
/// <param name="command">The command being logged.</param>
/// <param name="interceptionContext">Contextual information associated with the command.</param>
/// <param name="parameter">The parameter to log.</param>
public virtual void LogParameter<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext, DbParameter parameter)
{
    Check.NotNull(command, "command");
    Check.NotNull(interceptionContext, "interceptionContext");
    Check.NotNull(parameter, "parameter");
 
    // -- Name: [Value] (Type = {}, Direction = {}, IsNullable = {}, Size = {}, Precision = {} Scale = {})
    var builder = new StringBuilder();
    builder.Append("-- ")
        .Append(parameter.ParameterName)
        .Append(": '")
        .Append((parameter.Value == null || parameter.Value == DBNull.Value) ? "null" : parameter.Value)
        .Append("' (Type = ")
        .Append(parameter.DbType);
 
    if (parameter.Direction != ParameterDirection.Input)
    {
        builder.Append(", Direction = ").Append(parameter.Direction);
    }
 
    if (!parameter.IsNullable)
    {
        builder.Append(", IsNullable = false");
    }
 
    if (parameter.Size != 0)
    {
        builder.Append(", Size = ").Append(parameter.Size);
    }
 
    if (((IDbDataParameter)parameter).Precision != 0)
    {
        builder.Append(", Precision = ").Append(((IDbDataParameter)parameter).Precision);
    }
 
    if (((IDbDataParameter)parameter).Scale != 0)
    {
        builder.Append(", Scale = ").Append(((IDbDataParameter)parameter).Scale);
    }
 
    builder.Append(")").Append(Environment.NewLine);
 
    Write(builder.ToString());
}
 
/// <summary>
/// Called to log the result of executing a command. Override this method to change how results are
/// logged to <see cref="WriteAction" />.
/// </summary>
/// <typeparam name="TResult">The type of the operation's results.</typeparam>
/// <param name="command">The command being logged.</param>
/// <param name="interceptionContext">Contextual information associated with the command.</param>
public virtual void LogResult<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
    Check.NotNull(command, "command");
    Check.NotNull(interceptionContext, "interceptionContext");
 
    if (interceptionContext.Exception != null)
    {
        Write(Strings.CommandLogFailed(
            Stopwatch.ElapsedMilliseconds, interceptionContext.Exception.Message, Environment.NewLine));
    }
    else if (interceptionContext.TaskStatus.HasFlag(TaskStatus.Canceled))
    {
        Write(Strings.CommandLogCanceled(Stopwatch.ElapsedMilliseconds, Environment.NewLine));
    }
    else
    {
        var result = interceptionContext.Result;
        var resultString = (object)result == null
                               ? "null"
                               : (result is DbDataReader)
                                     ? result.GetType().Name
                                     : result.ToString();
        Write(Strings.CommandLogComplete(Stopwatch.ElapsedMilliseconds, resultString, Environment.NewLine));
    }
    Write(Environment.NewLine);
}

 

這篇就先講到這裡。


參考連結:

MSDN – Data Developer Center > Entity Framework > Logging and Intercepting Database Operations (EF6 Onwards)

 

以上

7 則留言:

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。