【问题标题】:How to write NULL to a column in a database using NLog Configuration API?如何使用 NLog 配置 API 将 NULL 写入数据库中的列?
【发布时间】:2014-01-03 17:06:35
【问题描述】:

我看过这个问题,但由于它使用 NLog.config 文件,我不能不将答案翻译成我需要的。 NLog - Write NULL to optional database column

在有人建议我回答我提出的另一个问题之后,我才开始使用 NLog。由于我在类库中使用它,并且我不想将 NLog.config 文件放置在使用该库的每个应用程序中,因此我使用的是配置 API。

我想要做的是在我的日志表中将一些列设置为 NULL,当它们不需要/指定时。我可以让 NLog 写一个空字符串,但不能写一个 NULL。

我尝试配置 NLog 来执行存储过程,但它似乎不起作用。

目前我正在使用用户名、密码等设置数据库目标,以及以下命令文本:

target.CommandText = "insert into Log(time_stamp,log_level,logger,message,exception_type,target_site,stack_trace,data,inner_exception) values(@time_stamp, @level, @logger, @message, @type, @target, @trace, @data, @inner);";

然后给目标添加参数,例如

var param = new DatabaseParameterInfo();
param.Name = "@time_stamp";
param.Layout = "${date}";
target.Parameters.Add(param);

所以这将只添加一个空字符串:

var param = new DatabaseParameterInfo();
param.Name = "@type";
param.Layout = "";
target.Parameters.Add(param);

但这不起作用,因为需要布局:

var param = new DatabaseParameterInfo();
param.Name = "@time_stamp";
param.Layout = null;
target.Parameters.Add(param);

我曾尝试执行存储过程,将命令文本更改如下(然后添加上述参数,包括空字符串而不是空值):

target.CommandText = "exec usp_InsertLog @time_stamp, @level, @logger, @message, @type, @target, @trace, @data, @inner";

根据 SQL Server Profiler(为清楚起见,换行符)会生成此内容:

exec sp_executesql N'
exec usp_InsertLog @time_stamp, @level, @logger, @message, @type, @target, @trace, @data, @inner',
N'@time_stamp nvarchar(19),@level nvarchar(5),@logger nvarchar(62),@message nvarchar(8),@type nvarchar(4000),@target nvarchar(4000),@trace nvarchar(4000),@data nvarchar(4000),@inner nvarchar(4000)',
@time_stamp=N'01/03/2014 17:00:38',@level=N'Debug',@logger=N'Testbed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null',@message=N'an error',@type=N'',@target=N'',@trace=N'',@data=N'',@inner=N''

如果我可以让存储过程工作,那么我可以让它插入 NULL 而不是空字符串,如果那是 NLog 传递的内容。

这里是目前的存储过程,不管值是否为空都不起作用:

ALTER PROCEDURE [dbo].[usp_InsertLog]
    @time_stamp datetime,
    @level nvarchar(100),
    @logger nvarchar(100),
    @message nvarchar(MAX),
    @type nvarchar(150) = NULL,
    @target nvarchar(100) = NULL,
    @trace nvarchar(MAX) = NULL,
    @data nvarchar(MAX) = NULL,
    @inner nvarchar(MAX) = NULL
AS

SET NOCOUNT ON

BEGIN TRY
insert into Log(
    time_stamp,
    log_level,
    logger,
    [message],
    exception_type,
    target_site,
    stack_trace,
    data,
    inner_exception) 
values(
    @time_stamp, 
    @level, 
    @logger, 
    @message, 
    @type, 
    @target, 
    @trace, 
    @data, 
    @inner) 
END TRY

有什么我错过的,或者更好的方法吗?

非常感谢!

【问题讨论】:

  • 该过程要求您发送带值的参数,如果参数没有值,则更改您的 SPROC 参数定义并提供默认值,例如 @timestamp DATETIME = GETDATE()
  • 为什么需要布局?我从来没有用过,这是什么数据库?
  • 我将创建一个返回 NULL 的自定义布局:public class NullLayout : Layout { protected override string GetFormattedMessage(LogEventInfo logEvent) { return "NULL"; } } 并在您的参数中使用它:param.Layout = new NullLayout();
  • 我已要求将此功能添加到 NLog 中,并且正在对其进行调查,例如param.Layout = null; 将 NULL 插入数据库。对于我的特定场景,我现在决定记录到两个不同的表,所以现在不需要 NULL。

标签: c# sql-server asp.net-mvc stored-procedures nlog


【解决方案1】:

这是我也在NLog - Write NULL to optional database column上发布的一个解决方案,我希望它可以有用,因为我认为存储过程解决方案就像用大锤敲碎坚果。

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

这样你的 NLog 配置文件上的 commandText 看起来像:

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

【讨论】:

  • 谢谢 - 这太愉快和容易了
  • 这仅适用于空字符串。可选整数呢?似乎 NLog 将可空整数默认为 0。有时我确实希望 0 成为有效整数之一。遇到这种情况该怎么办?
【解决方案2】:

我使用稍微不同的方法。


因为我不喜欢编写查询,所以我创建了一个为我执行此操作的扩展,我的 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:nullnull 表示它可以为空

没有&lt;commandText&gt; 元素,但我使用此扩展从参数自动创建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();

【讨论】:

    【解决方案3】:

    在sql server中你可以做这样的事情

    DECLARE @Var1 INT, @Var2 INT, @Var3 INT, @Var4 VARCHAR(10)
           , @Var5 VARCHAR(5), @SubsituteVal_Int INT , @SubsituteVal_Char VARCHAR(10)
    SET @Var1 = 1;
    SET @Var3 = 3;               --<-- Didnt Assign any Values to @Var2 and @Var4
    SET @Var5 = 'Fifth';
    SET @SubsituteVal_Int = 100          --<-- Provided Substitute values if actual 
    SET @SubsituteVal_Char = 'No Value';    -- values are null.
    

    /使用 COALESCE 函数提供实际值和替代值/

    DECLARE @t TABLE(Column1 INT, Column2 INT, Column3 INT, Column4 VARCHAR(10), Column5 VARCHAR(10))
    INSERT INTO @t
    VALUES (COALESCE(@Var1, @SubsituteVal_Int),COALESCE(@Var2, @SubsituteVal_Int)
           ,COALESCE(@Var3, @SubsituteVal_Int),COALESCE(@Var4, @SubsituteVal_Char),COALESCE(@Var5, @SubsituteVal_Char))
    SELECT * FROM @t
    

    结果集

    ╔═════════╦═════════╦═════════╦══════════╦═════════╗
    ║ Column1 ║ Column2 ║ Column3 ║ Column4  ║ Column5 ║
    ╠═════════╬═════════╬═════════╬══════════╬═════════╣
    ║       1 ║     100 ║       3 ║ No Value ║ Fifth   ║
    ╚═════════╩═════════╩═════════╩══════════╩═════════╝
    

    你的过程

    对于您的程序,您可以执行类似的操作

    BEGIN TRY
    insert into Log(
        time_stamp,
        log_level,
        logger,
        [message],
        exception_type,
        target_site,
        stack_trace,
        data,
        inner_exception) 
    values(
         COALESCE(@time_stamp, @Subsitute_Val1) 
        ,COALESCE(@level, @Subsitute_Val2)
        ,COALESCE(@logger, @Subsitute_Val3)
        .
        .. so on.... ) 
    END TRY
    

    【讨论】:

      【解决方案4】:

      NLog 版本。 4.7.4 添加了参数选项AllowDbNull,所以你可以这样做:

      var param = new DatabaseParameterInfo();
      param.Name = "@type";
      param.Layout = "";
      param.AllowDbNull = true;
      target.Parameters.Add(param);
      

      allowDbNull="true" 时,Nlog 会自动将空字符串/无输出转换为 DbNull-value。

      【讨论】:

        猜你喜欢
        • 2011-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多