網頁

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 則留言: