diff --git a/src/NLog/Targets/DatabaseParameterInfo.cs b/src/NLog/Targets/DatabaseParameterInfo.cs
index 24e1684129..17d353d0f4 100644
--- a/src/NLog/Targets/DatabaseParameterInfo.cs
+++ b/src/NLog/Targets/DatabaseParameterInfo.cs
@@ -134,6 +134,13 @@ public DatabaseParameterInfo(string parameterName, Layout parameterLayout)
[DefaultValue(null)]
public CultureInfo Culture { get; set; }
+ ///
+ /// Gets or sets whether empty value should translate into DbNull. Requires database column to allow NULL values.
+ ///
+ ///
+ [DefaultValue(false)]
+ public bool AllowDbNull { get; set; }
+
internal bool SetDbType(IDbDataParameter dbParameter)
{
if (!string.IsNullOrEmpty(DbType))
diff --git a/src/NLog/Targets/DatabaseTarget.cs b/src/NLog/Targets/DatabaseTarget.cs
index 391c927d06..1cc311345f 100644
--- a/src/NLog/Targets/DatabaseTarget.cs
+++ b/src/NLog/Targets/DatabaseTarget.cs
@@ -1094,43 +1094,38 @@ protected virtual IDbDataParameter CreateDatabaseParameter(IDbCommand command, D
/// Parameter configuration info.
protected internal virtual object GetDatabaseParameterValue(LogEventInfo logEvent, DatabaseParameterInfo parameterInfo)
{
- return RenderObjectValue(logEvent, parameterInfo.Name, parameterInfo.Layout, parameterInfo.ParameterType, parameterInfo.Format, parameterInfo.Culture);
+ return RenderObjectValue(logEvent, parameterInfo.Name, parameterInfo.Layout, parameterInfo.ParameterType, parameterInfo.Format, parameterInfo.Culture, parameterInfo.AllowDbNull);
}
private object GetDatabaseObjectPropertyValue(LogEventInfo logEvent, DatabaseObjectPropertyInfo connectionInfo)
{
- return RenderObjectValue(logEvent, connectionInfo.Name, connectionInfo.Layout, connectionInfo.PropertyType, connectionInfo.Format, connectionInfo.Culture);
+ return RenderObjectValue(logEvent, connectionInfo.Name, connectionInfo.Layout, connectionInfo.PropertyType, connectionInfo.Format, connectionInfo.Culture, false);
}
- private object RenderObjectValue(LogEventInfo logEvent, string propertyName, Layout valueLayout, Type valueType, string valueFormat, IFormatProvider formatProvider)
+ private object RenderObjectValue(LogEventInfo logEvent, string propertyName, Layout valueLayout, Type valueType, string valueFormat, IFormatProvider formatProvider, bool allowDbNull)
{
- if (string.IsNullOrEmpty(valueFormat) && valueType == typeof(string))
+ if (string.IsNullOrEmpty(valueFormat) && valueType == typeof(string) && !allowDbNull)
{
return RenderLogEvent(valueLayout, logEvent) ?? string.Empty;
}
formatProvider = formatProvider ?? logEvent.FormatProvider ?? LoggingConfiguration?.DefaultCultureInfo;
- if (valueLayout.TryGetRawValue(logEvent, out var rawValue))
+ try
{
- try
+ if (TryRenderObjectRawValue(logEvent, valueLayout, valueType, valueFormat, formatProvider, allowDbNull, out var rawValue))
{
- if (ReferenceEquals(rawValue, DBNull.Value))
- {
- return rawValue;
- }
-
- return PropertyTypeConverter.Convert(rawValue, valueType, valueFormat, formatProvider) ?? CreateDefaultValue(valueType);
+ return rawValue;
}
- catch (Exception ex)
- {
- if (ex.MustBeRethrownImmediately())
- throw;
+ }
+ catch (Exception ex)
+ {
+ if (ex.MustBeRethrownImmediately())
+ throw;
- InternalLogger.Warn(ex, " DatabaseTarget: Failed to convert raw value for '{0}' into {1}", propertyName, valueType);
- if (ExceptionMustBeRethrown(ex))
- throw;
- }
+ InternalLogger.Warn(ex, " DatabaseTarget: Failed to convert raw value for '{0}' into {1}", propertyName, valueType);
+ if (ExceptionMustBeRethrown(ex))
+ throw;
}
try
@@ -1139,10 +1134,10 @@ private object RenderObjectValue(LogEventInfo logEvent, string propertyName, Lay
string parameterValue = RenderLogEvent(valueLayout, logEvent);
if (string.IsNullOrEmpty(parameterValue))
{
- return CreateDefaultValue(valueType);
+ return CreateDefaultValue(valueType, allowDbNull);
}
- return PropertyTypeConverter.Convert(parameterValue, valueType, valueFormat, formatProvider) ?? DBNull.Value;
+ return PropertyTypeConverter.Convert(parameterValue, valueType, valueFormat, formatProvider) ?? CreateDefaultValue(valueType, allowDbNull);
}
catch (Exception ex)
{
@@ -1154,18 +1149,45 @@ private object RenderObjectValue(LogEventInfo logEvent, string propertyName, Lay
if (ExceptionMustBeRethrown(ex))
throw;
- return CreateDefaultValue(valueType);
+ return CreateDefaultValue(valueType, allowDbNull);
}
}
+ private bool TryRenderObjectRawValue(LogEventInfo logEvent, Layout valueLayout, Type valueType, string valueFormat, IFormatProvider formatProvider, bool allowDbNull, out object rawValue)
+ {
+ if (valueLayout.TryGetRawValue(logEvent, out rawValue))
+ {
+ if (ReferenceEquals(rawValue, DBNull.Value))
+ {
+ return true;
+ }
+
+ if (rawValue == null)
+ {
+ rawValue = CreateDefaultValue(valueType, allowDbNull);
+ return true;
+ }
+
+ if (valueType == typeof(string))
+ {
+ return rawValue is string;
+ }
+
+ rawValue = PropertyTypeConverter.Convert(rawValue, valueType, valueFormat, formatProvider) ?? CreateDefaultValue(valueType, allowDbNull);
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Create Default Value of Type
///
- ///
- ///
- private static object CreateDefaultValue(Type dbParameterType)
+ private static object CreateDefaultValue(Type dbParameterType, bool allowDbNull)
{
- if (dbParameterType == typeof(string))
+ if (allowDbNull)
+ return DBNull.Value;
+ else if (dbParameterType == typeof(string))
return string.Empty;
else if (dbParameterType.IsValueType())
return Activator.CreateInstance(dbParameterType);
diff --git a/tests/NLog.UnitTests/Targets/DatabaseTargetTests.cs b/tests/NLog.UnitTests/Targets/DatabaseTargetTests.cs
index 37ed97fe11..a1ace521f3 100644
--- a/tests/NLog.UnitTests/Targets/DatabaseTargetTests.cs
+++ b/tests/NLog.UnitTests/Targets/DatabaseTargetTests.cs
@@ -591,7 +591,7 @@ Add Parameter Parameter #1
[InlineData("${counter}", DbType.Int32, 1)]
[InlineData("${counter}", DbType.Int64, (long)1)]
[InlineData("${counter:norawvalue=true}", DbType.Int16, (short)1)] //fallback
- [InlineData("${counter}", DbType.VarNumeric, 1, true)]
+ [InlineData("${counter}", DbType.VarNumeric, 1, false, true)]
[InlineData("${counter}", DbType.AnsiString, "1")]
[InlineData("${level}", DbType.AnsiString, "Debug")]
[InlineData("${level}", DbType.Int32, 1)]
@@ -607,12 +607,23 @@ Add Parameter Parameter #1
[InlineData("${event-properties:almostAsIntProp}", DbType.Int32, 124)]
[InlineData("${event-properties:almostAsIntProp}", DbType.Int64, (long)124)]
[InlineData("${event-properties:almostAsIntProp}", DbType.AnsiString, " 124 ")]
- public void GetParameterValueTest(string layout, DbType dbtype, object expected, bool convertToDecimal = false)
+ [InlineData("${event-properties:emptyprop}", DbType.AnsiString, "")]
+ [InlineData("${event-properties:emptyprop}", DbType.AnsiString, "", true)]
+ [InlineData("${event-properties:NullRawValue}", DbType.AnsiString, "")]
+ [InlineData("${event-properties:NullRawValue}", DbType.Int32, 0)]
+ [InlineData("${event-properties:NullRawValue}", DbType.AnsiString, null, true)]
+ [InlineData("${event-properties:NullRawValue}", DbType.Int32, null, true)]
+ [InlineData("${event-properties:NullRawValue}", DbType.Guid, null, true)]
+ [InlineData("", DbType.AnsiString, null, true)]
+ [InlineData("", DbType.Int32, null, true)]
+ [InlineData("", DbType.Guid, null, true)]
+ public void GetParameterValueTest(string layout, DbType dbtype, object expected, bool allowDbNull = false, bool convertToDecimal = false)
{
// Arrange
var logEventInfo = new LogEventInfo(LogLevel.Debug, "logger1", "message 2");
logEventInfo.Properties["intprop"] = 123;
logEventInfo.Properties["boolprop"] = true;
+ logEventInfo.Properties["emptyprop"] = "";
logEventInfo.Properties["almostAsIntProp"] = " 124 ";
logEventInfo.Properties["dateprop"] = new DateTime(2018, 12, 30, 13, 34, 56);
@@ -622,6 +633,7 @@ public void GetParameterValueTest(string layout, DbType dbtype, object expected,
DbType = dbtype.ToString(),
Layout = layout,
Name = parameterName,
+ AllowDbNull = allowDbNull,
};
databaseParameterInfo.SetDbType(new MockDbConnection().CreateCommand().CreateParameter());
@@ -635,12 +647,12 @@ public void GetParameterValueTest(string layout, DbType dbtype, object expected,
expected = (decimal)(int)expected;
}
- Assert.Equal(expected, result);
+ Assert.Equal(expected ?? DBNull.Value, result);
}
[Theory]
[MemberData(nameof(ConvertFromStringTestCases))]
- public void GetParameterValueFromStringTest(string value, DbType dbType, object expected, string format = null, CultureInfo cultureInfo = null)
+ public void GetParameterValueFromStringTest(string value, DbType dbType, object expected, string format = null, CultureInfo cultureInfo = null, bool? allowDbNull = null)
{
var culture = System.Threading.Thread.CurrentThread.CurrentCulture;
@@ -655,6 +667,7 @@ public void GetParameterValueFromStringTest(string value, DbType dbType, object
Format = format,
DbType = dbType.ToString(),
Culture = cultureInfo,
+ AllowDbNull = allowDbNull ?? false,
};
databaseParameterInfo.SetDbType(new MockDbConnection().CreateCommand().CreateParameter());
@@ -704,7 +717,10 @@ public static IEnumerable