NLog - 将NULL写入可选数据库列

时间:2011-01-28 18:08:12

标签: c# .net asp.net logging nlog

我使用NLog登录ASP.Net应用程序并使用Microsoft Sql Server的数据库目标。

我有一些可选的记录参数,并不总是指定。我希望这些在没有提供时写为null,但是NLog似乎总是把它们写成空字符串。

有没有办法将其配置为将null写为默认值?

参考:https://github.com/nlog/NLog/wiki/Database-target

5 个答案:

答案 0 :(得分:12)

这是一个老问题,但由于给出的解决方案有点“ hacky '我想提供自己的功能,我认为它比db程序更简单,使用案例更优雅。

您可以尝试使用比较2个表达式的NULLIF函数编写NULL,如果它们相等则返回NULL,否则返回第一个表达式(msdn NULLIF page)。

这样,NLog配置文件中的commandText将如下所示:

INSERT INTO [dbo].[log] ([message], [optional]) 
VALUES (@message, NULLIF(@optional, ''))

答案 1 :(得分:7)

NLog使用StringBuilder来创建参数值。即使未指定参数,它也会将值初始化为builder.ToString(),这是一个空字符串。

您可以像这样更改commandText:

INSERT INTO [dbo].[log] ([message], [optional]) 
VALUES 
(
    @message, 
    case 
      when len(@optional) = 0 then null 
      else @optional 
    end
)

虽然对我来说似乎是个黑客。我希望有更好的解决方案。

答案 2 :(得分:2)

[编辑]

也许比我在下面提出的更明显的解决方案是从使用INSERT语句转换为使用数据库过程进行记录。如果您使用数据库过程,那么您可以处理从空字符串调到自己的null。我不确定您是否可以将数据库过程与NLog的数据库目标一起使用。 Log4net支持它,所以我的猜测是NLog也可以。

这是一个例子(在链接问题的答案中)我找到了使用NLog使用存储过程登录数据库的人的配置。

http://nlog-forum.1685105.n2.nabble.com/Using-a-stored-procedure-for-the-DB-Target-td2621725.html

我在这里看到:

http://nlog.codeplex.com/workitem/5418

抱怨它不起作用(至少在NLog 2.0测试版中)。

两个示例之间的一个区别是工作示例使用“exec LoggingProcedureName ...”而非工作示例使用“LoggingProcedureName ...”

希望这会有所帮助。

[编辑结束]

我无法评论为什么NLog写入emptry字符串而不是null或者如何使NLog写入null而不是空字符串,但我想知道你是否可以通过其他配置以他们想要的方式工作?

日志记录参数何时可选?您的代码中是否存在某些位置,您始终记录某些值以及其他未记录某些值的位置?您(作为开发人员)能否知道哪些可选参数适用于您的应用程序的哪些部分?

您是否可以配置多个数据库目标,每个目标都指定了“正确”参数?然后,您可以将Loggers指向适合代码位置的特定数据库目标。

假设您的应用程序(按名称空间)划分为(通常)在“之前”,“期间”和“之后”执行的代码。

在“之前”代码中,您可能正在记录参数A.在“期间”代码中,您可能正在记录参数B.在“之后”代码中,您可能正在记录参数C.因此,您的记录表可能有以下列:

DateTime, Logger, LogLevel, A, B, C, Message, Exception

现在,您有一个数据库目标,可以为每个日志记录语句插入所有这些值。

如果您有三个数据库目标插入如下所示的值,那该怎么办:

DataTime, Logger, LogLevel, A, Message, Exception
DataTime, Logger, LogLevel, B, Message, Exception
DataTime, Logger, LogLevel, C, Message, Exception

您可以将您的部分配置为:

  <rules>
    <logger name="Before.*" minlevel="Trace" writeTo="databaseA" />
    <logger name="During.*" minlevel="Trace" writeTo="databaseB" />
    <logger name="After.*" minlevel="Trace" writeTo="databaseC" />
  </rules>

显然,这个想法可能有几个问题:

  1. 可能(或很容易)将记录器分开以匹配参数的“可选性”。

  2. 可选参数的组合太多,以使其可行(可能与1相同)。

  3. 一次激活数据库目标日志可能不是一个好主意。也许这会导致性能问题。

  4. 嗯,这就是我的全部。我不知道我的想法是否会奏效,更不用说如果它是实用的。

    可能更好的解决方案是NLog允许在每个数据库参数上允许你说“发送空而不是空字符串”的额外属性。

    我想我应该建议您也可以在NLog forum中提出这个问题。该论坛上的“Craig”今天早些时候提出了同样(或类似)的问题。也许你是克雷格。

答案 3 :(得分:1)

NLog版本。 4.7.4添加了参数选项df1 %<>% mutate(responded_at= as.Date(column_name)) ,因此您可以这样做:

AllowDbNull

<parameter name="@exception" layout="${exception:format=tostring}" allowDbNull="true" /> <parameter name="@correlationid" layout="${activityid}" dbType="DbType.Guid" allowDbNull="true" /> 时,Nlog会自动将空字符串/无输出转换为DbNull值。

答案 4 :(得分:0)

我使用的方法略有不同。

因为我不喜欢编写查询,所以我创建了一个扩展程序,为我执行此操作,我的NLog配置如下所示:

<target xsi:type="Database" name="Log" commandText="[dbo].[Log]" dbProvider="System.Data.SqlClient" connectionString="..">
  <parameter name="@Timestamp" layout="${longdate:universalTime=true}" />
  <parameter name="@LogLevel" layout="${level:uppercase=true}" />
  <parameter name="@Logger" layout="${logger}" />
  <parameter name="@Message" layout="${message}" />
  <parameter name="@Exception:null" layout="${onexception:${exceptionLayout}}" />
</target>

请注意两件事:

  • 必须遵循格式commandText="[dbo].[Log]"
  • [Schema].[Table]
  • @Exception:null null表示可以为空的<{li}

没有<commandText>元素,而是使用此扩展程序自动从参数创建INSERT

public static class NLogExtensions
{
    // The commandText attribute must conatain at least the table name. 
    // Each identifier must be enclosed in square brackets: [schemaName].[tableName].

    public static void GenerateDatabaseTargetInsertQueries(this NLog.Config.LoggingConfiguration config)
    {
        var tableNameMatcher = new Regex(@"^(\[(?<schemaName>.+?)\].)?\[(?<tableName>.+?)\]$");

        var autoCommandTextDatabaseTargets =
            config.AllTargets
                .OfType<DatabaseTarget>()
                .Where(x => tableNameMatcher.IsMatch(x.CommandText()))
                .Select(x => x);

        foreach (var databaseTarget in autoCommandTextDatabaseTargets)
        {
            databaseTarget.CommandText = databaseTarget.CreateCommandText();
        }
    }

    internal static string CommandText(this DatabaseTarget databaseTarget)
    {
        return ((NLog.Layouts.SimpleLayout)databaseTarget.CommandText).OriginalText;
    }

    internal static string CreateCommandText(this DatabaseTarget databaseTarget)
    {
        const string insertQueryTemplate = "INSERT INTO {0}({1}) VALUES({2})";

        return string.Format(
                insertQueryTemplate,
                databaseTarget.CommandText(),
                string.Join(", ", databaseTarget.Parameters.Select(x => x.Name())),
                string.Join(", ", databaseTarget.Parameters.Select(x =>
                {
                    var sql = 
                        x.Nullable() 
                        ? string.Format("NULLIF({0}, '')", x.FullName()) 
                        : x.FullName();

                    // Rename the SqlParameter because otherwise SqlCommand will complain about it.
                    x.Name = x.FullName();

                    return sql;
                })));
    }
}

public static class NLogDatabaseTarget
{
    public static void GenerateInsertQueries()
    {
        NLog.LogManager.Configuration.GenerateDatabaseTargetInsertQueries();
    }
}

另外,它可以解析参数名称并为可以为空的参数插入NULLIF

另一个扩展程序可以帮助我解析它:

public static class DatabaseParameterInfoExtensions
{
    // https://regex101.com/r/wgoA3q/2

    private static readonly Regex ParamRegex = new Regex("^(?<prefix>.)(?<name>[a-z0-9_\-]+)(?:[:](?<null>null))?", RegexOptions.IgnoreCase);

    public static string Prefix(this DatabaseParameterInfo parameter)
    {
        return ParamRegex.Match(parameter.Name).Groups["prefix"].Value;
    }

    public static string Name(this DatabaseParameterInfo parameter)
    {
        return ParamRegex.Match(parameter.Name).Groups["name"].Value;
    }

    public static string FullName(this DatabaseParameterInfo parameter)
    {
        return string.Format("{0}{1}", parameter.Prefix(), parameter.Name());
    }

    public static bool Nullable(this DatabaseParameterInfo parameter)
    {
        return ParamRegex.Match(parameter.Name).Groups["null"].Success;
    }
}

这意味着您需要将commandText="[dbo].[Log]"属性添加到数据库目标,删除查询并将:null添加到可为空的列的参数名称。

在代码中你只需要调用它,扩展就可以发挥作用。

NLogDatabaseTarget.GenerateInsertQueries();