如何使用NLog Configuration API将NULL写入数据库中的列?

时间:2014-01-03 17:06:35

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

我已经看过这个问题,但是因为它使用了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

我有什么遗漏,或者更好的方法吗?

非常感谢!

4 个答案:

答案 0 :(得分:5)

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

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

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

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

答案 1 :(得分:1)

我使用的方法略有不同。

因为我不喜欢编写查询,所以我创建了一个扩展程序,为我执行此操作,我的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();

答案 2 :(得分:0)

在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

答案 3 :(得分:0)

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值。