Skip to content

Commit 61a268e

Browse files
committed
DatabaseTarget - Added support for DbCommand Properties
1 parent fa8485c commit 61a268e

File tree

3 files changed

+65
-23
lines changed

3 files changed

+65
-23
lines changed

src/NLog/Targets/DatabaseObjectPropertyInfo.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ public DatabaseObjectPropertyInfo()
9191
[DefaultValue(null)]
9292
public CultureInfo Culture { get; set; }
9393

94-
internal bool SetPropertyValue(IDbConnection dbConnection, object propertyValue)
94+
internal bool SetPropertyValue(object dbObject, object propertyValue)
9595
{
96-
var dbConnectionType = dbConnection.GetType();
96+
var dbConnectionType = dbObject.GetType();
9797
var propertySetterCache = _propertySetter;
9898
if (!propertySetterCache.Equals(Name, dbConnectionType))
9999
{
@@ -102,7 +102,7 @@ internal bool SetPropertyValue(IDbConnection dbConnection, object propertyValue)
102102
_propertySetter = propertySetterCache;
103103
}
104104

105-
return propertySetterCache.PropertySetter?.SetPropertyValue(dbConnection, propertyValue) ?? false;
105+
return propertySetterCache.PropertySetter?.SetPropertyValue(dbObject, propertyValue) ?? false;
106106
}
107107

108108
private struct PropertySetterCacheItem

src/NLog/Targets/DatabaseTarget.cs

+36-20
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,14 @@ public Layout DBPassword
280280
[ArrayParameter(typeof(DatabaseObjectPropertyInfo), "connectionproperty")]
281281
public IList<DatabaseObjectPropertyInfo> ConnectionProperties { get; } = new List<DatabaseObjectPropertyInfo>();
282282

283+
/// <summary>
284+
/// Gets the collection of properties. Each item contains a mapping
285+
/// between NLog layout and a property on the DbCommand instance
286+
/// </summary>
287+
/// <docgen category='Connection Options' order='10' />
288+
[ArrayParameter(typeof(DatabaseObjectPropertyInfo), "commandproperty")]
289+
public IList<DatabaseObjectPropertyInfo> CommandProperties { get; } = new List<DatabaseObjectPropertyInfo>();
290+
283291
/// <summary>
284292
/// Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance.
285293
/// </summary>
@@ -358,29 +366,29 @@ internal IDbConnection OpenConnection(string connectionString, LogEventInfo logE
358366
connection.ConnectionString = connectionString;
359367
if (ConnectionProperties?.Count > 0)
360368
{
361-
ApplyConnectionProperties(connection, logEventInfo ?? LogEventInfo.CreateNullEvent());
369+
ApplyDatabaseObjectProperties(connection, ConnectionProperties, logEventInfo ?? LogEventInfo.CreateNullEvent());
362370
}
363371

364372
connection.Open();
365373
return connection;
366374
}
367375

368-
private void ApplyConnectionProperties(IDbConnection connection, LogEventInfo logEventInfo)
376+
private void ApplyDatabaseObjectProperties(object databaseObject, IList<DatabaseObjectPropertyInfo> objectProperties, LogEventInfo logEventInfo)
369377
{
370-
for (int i = 0; i < ConnectionProperties.Count; ++i)
378+
for (int i = 0; i < objectProperties.Count; ++i)
371379
{
372-
var propertyInfo = ConnectionProperties[i];
380+
var propertyInfo = objectProperties[i];
373381
try
374382
{
375-
var propertyValue = GetDatabaseConnectionValue(logEventInfo, propertyInfo);
376-
if (!propertyInfo.SetPropertyValue(connection, propertyValue))
383+
var propertyValue = GetDatabaseObjectPropertyValue(logEventInfo, propertyInfo);
384+
if (!propertyInfo.SetPropertyValue(databaseObject, propertyValue))
377385
{
378-
InternalLogger.Warn("DatabaseTarget(Name={0}): Failed to lookup property {1} on {2}", Name, propertyInfo.Name, connection.GetType());
386+
InternalLogger.Warn("DatabaseTarget(Name={0}): Failed to lookup property {1} on {2}", Name, propertyInfo.Name, databaseObject.GetType());
379387
}
380388
}
381389
catch (Exception ex)
382390
{
383-
InternalLogger.Error(ex, "DatabaseTarget(Name={0}): Failed to assign value for property {1} on {2}", Name, propertyInfo.Name, connection.GetType());
391+
InternalLogger.Error(ex, "DatabaseTarget(Name={0}): Failed to assign value for property {1} on {2}", Name, propertyInfo.Name, databaseObject.GetType());
384392
if (ex.MustBeRethrown())
385393
throw;
386394
}
@@ -699,7 +707,7 @@ private void WriteLogEventBatchToDatabase(IList<AsyncLogEventInfo> logEvents, st
699707
{
700708
for (int i = 0; i < logEvents.Count; ++i)
701709
{
702-
ExecuteDbCommandWithParameters(logEvents[i].LogEvent, dbTransaction);
710+
ExecuteDbCommandWithParameters(logEvents[i].LogEvent, _activeConnection, dbTransaction);
703711
}
704712

705713
dbTransaction?.Commit();
@@ -767,7 +775,7 @@ private void WriteLogEventToDatabase(LogEventInfo logEvent, string connectionStr
767775
{
768776
EnsureConnectionOpen(connectionString, logEvent);
769777

770-
ExecuteDbCommandWithParameters(logEvent, null);
778+
ExecuteDbCommandWithParameters(logEvent, _activeConnection, null);
771779

772780
transactionScope.Complete(); //not really needed as there is no transaction at all.
773781
}
@@ -790,12 +798,9 @@ private void WriteLogEventToDatabase(LogEventInfo logEvent, string connectionStr
790798
/// <summary>
791799
/// Write logEvent to database
792800
/// </summary>
793-
private void ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbTransaction dbTransaction)
801+
private void ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbConnection dbConnection, IDbTransaction dbTransaction)
794802
{
795-
var commandText = RenderLogEvent(CommandText, logEvent);
796-
InternalLogger.Trace("DatabaseTarget(Name={0}): Executing {1}: {2}", Name, CommandType, commandText);
797-
798-
using (IDbCommand command = CreateDbCommandWithParameters(logEvent, CommandType, commandText, Parameters))
803+
using (IDbCommand command = CreateDbCommand(logEvent, dbConnection))
799804
{
800805
if (dbTransaction != null)
801806
command.Transaction = dbTransaction;
@@ -805,11 +810,22 @@ private void ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbTransactio
805810
}
806811
}
807812

813+
internal IDbCommand CreateDbCommand(LogEventInfo logEvent, IDbConnection dbConnection)
814+
{
815+
var commandText = RenderLogEvent(CommandText, logEvent);
816+
InternalLogger.Trace("DatabaseTarget(Name={0}): Executing {1}: {2}", Name, CommandType, commandText);
817+
return CreateDbCommandWithParameters(logEvent, dbConnection, CommandType, commandText, Parameters);
818+
}
819+
808820
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "It's up to the user to ensure proper quoting.")]
809-
private IDbCommand CreateDbCommandWithParameters(LogEventInfo logEvent, CommandType commandType, string dbCommandText, IList<DatabaseParameterInfo> databaseParameterInfos)
821+
private IDbCommand CreateDbCommandWithParameters(LogEventInfo logEvent, IDbConnection dbConnection, CommandType commandType, string dbCommandText, IList<DatabaseParameterInfo> databaseParameterInfos)
810822
{
811-
var dbCommand = _activeConnection.CreateCommand();
823+
var dbCommand = dbConnection.CreateCommand();
812824
dbCommand.CommandType = commandType;
825+
if (CommandProperties?.Count > 0)
826+
{
827+
ApplyDatabaseObjectProperties(dbCommand, CommandProperties, logEvent);
828+
}
813829
dbCommand.CommandText = dbCommandText;
814830

815831
for (int i = 0; i < databaseParameterInfos.Count; ++i)
@@ -965,7 +981,7 @@ private void RunInstallCommands(InstallationContext installationContext, IEnumer
965981

966982
installationContext.Trace("DatabaseTarget(Name={0}) - Executing {1} '{2}'", Name, commandInfo.CommandType, commandText);
967983

968-
using (IDbCommand command = CreateDbCommandWithParameters(logEvent, commandInfo.CommandType, commandText, commandInfo.Parameters))
984+
using (IDbCommand command = CreateDbCommandWithParameters(logEvent, _activeConnection, commandInfo.CommandType, commandText, commandInfo.Parameters))
969985
{
970986
try
971987
{
@@ -1081,7 +1097,7 @@ protected internal virtual object GetDatabaseParameterValue(LogEventInfo logEven
10811097
return RenderObjectValue(logEvent, parameterInfo.Name, parameterInfo.Layout, parameterInfo.ParameterType, parameterInfo.Format, parameterInfo.Culture);
10821098
}
10831099

1084-
private object GetDatabaseConnectionValue(LogEventInfo logEvent, DatabaseObjectPropertyInfo connectionInfo)
1100+
private object GetDatabaseObjectPropertyValue(LogEventInfo logEvent, DatabaseObjectPropertyInfo connectionInfo)
10851101
{
10861102
return RenderObjectValue(logEvent, connectionInfo.Name, connectionInfo.Layout, connectionInfo.PropertyType, connectionInfo.Format, connectionInfo.Culture);
10871103
}
@@ -1163,7 +1179,7 @@ private static object CreateDefaultValue(Type dbParameterType)
11631179
///
11641180
/// Transactions aren't in .NET Core: https://github.com/dotnet/corefx/issues/2949
11651181
/// </summary>
1166-
private class TransactionScope : IDisposable
1182+
private sealed class TransactionScope : IDisposable
11671183
{
11681184
private readonly TransactionScopeOption suppress;
11691185

tests/NLog.UnitTests/Targets/DatabaseTargetTests.cs

+26
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,32 @@ public void AccessTokenWithInvalidTypeCannotBeSet()
12031203
Assert.Throws<FormatException>(() => databaseTarget.OpenConnection(".", null));
12041204
}
12051205

1206+
[Fact]
1207+
public void CommandTimeoutShouldBeSet()
1208+
{
1209+
// Arrange
1210+
var commandTimeout = "123";
1211+
MockDbConnection.ClearLog();
1212+
var databaseTarget = new DatabaseTarget
1213+
{
1214+
DBProvider = typeof(MockDbConnection).AssemblyQualifiedName,
1215+
CommandText = "command1",
1216+
};
1217+
databaseTarget.CommandProperties.Add(new DatabaseObjectPropertyInfo() { Name = "CommandTimeout", Layout = commandTimeout, PropertyType = typeof(int) });
1218+
databaseTarget.Initialize(new LoggingConfiguration());
1219+
1220+
// Act
1221+
var connection = databaseTarget.OpenConnection(".", null);
1222+
var command1 = databaseTarget.CreateDbCommand(LogEventInfo.CreateNullEvent(), connection);
1223+
var command2 = databaseTarget.CreateDbCommand(LogEventInfo.CreateNullEvent(), connection); // Twice because we use compiled method on 2nd attempt
1224+
1225+
// Assert
1226+
var sqlCommand1 = Assert.IsType<MockDbCommand>(command1);
1227+
Assert.Equal(commandTimeout, sqlCommand1.CommandTimeout.ToString()); // Verify dynamic setter method invoke assigns correctly
1228+
var sqlCommand2 = Assert.IsType<MockDbCommand>(command2);
1229+
Assert.Equal(commandTimeout, sqlCommand2.CommandTimeout.ToString()); // Verify compiled method also assigns correctly
1230+
}
1231+
12061232
[Fact]
12071233
public void SqlServerShorthandNotationTest()
12081234
{

0 commit comments

Comments
 (0)