I use NLog everywhere and I really don't like writing the inserts everytime. You need to write the same variable three times: two for the insert and one for the parameter for example:
<target xsi:type="Database" name="Log" dbProvider="System.Data.SqlClient" connectionString="...">
<commandText>
INSERT
INTO [dbo].[Log]([Foo], [Bar], ...)
VALUES(@FOO, @BAR, ...)
</commandText>
<parameter name="@FOO" layout="..." />
<parameter name="@BAR" layout="..." />
...
</target>
It's very error-prone (I always make a mistake somewhere by forgetting one or two of them) and hard to maintain.
I just want to add the parameters and create the INSERT
automatically because it's nothing unusual and can be automated so I came up with this idea:
The commandText
must not be empty for database targets and I also need to somehow specify the table name. So initially I put the table name there.
<target xsi:type="Database" name="TestLog" commandText="[dbo].[TestLog]" dbProvider="System.Data.SqlClient" connectionString="Data Source=(local);Initial Catalog=TestDb;Integrated Security=True;Connect Timeout=30">
<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" layout="${onexception:${exceptionLayout}}" />
</target>
In this case the INSERT
would be:
INSERT INTO [dbo].[TestLog](Timestamp, LogLevel, Logger, Message, Exception)
VALUES(@Timestamp, @LogLevel, @Logger, @Message, @Exception)
Next I use a set of extensions to update the commandText
. Fortunatelly the CommandText
property has a public setter.
static class NLogExtensions
{
public static void UpdateCommandText(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();
}
}
public static string CommandText(this DatabaseTarget databaseTarget)
{
return ((NLog.Layouts.SimpleLayout)databaseTarget.CommandText).OriginalText;
}
public 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.TrimStart('@'))),
string.Join(", ", databaseTarget.Parameters.Select(x => x.Name)));
}
}
Then I use it like this:
var config = NLog.LogManager.Configuration;
config.UpdateCommandText();
var logger = NLog.LogManager.GetLogger("Program");
logger.Debug("Hallo NLog!");
I cannot use C# 6
CreateCommandText()
beingpublic
? \$\endgroup\$private
indeed - actually it should beinternal
\$\endgroup\$