Skip to content

Commit 0b8a840

Browse files
Replace all AddWithValue with explicit SqlParameter types, fix alert re-firing
AddWithValue infers types incorrectly — for DateTime it sends legacy datetime (3.33ms precision) instead of datetime2, causing the deadlock dedup cutoff to round and re-collect the same event every cycle. Replaced all 28 usages across 7 files with explicitly typed SqlParameter. Fixed alert re-firing: ServerTab used CollectionTime (always new) instead of DeadlockTime/EventTime for ack comparison. Overview deadlock count query also filtered by collection_time instead of deadlock_time, inflating counts with duplicate rows and triggering spurious popup alerts. Tested: zero AddWithValue remaining, all 3 projects build clean, dedup confirmed working under HammerDB load (97 new deadlocks collected, zero duplicates). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ee58115 commit 0b8a840

9 files changed

Lines changed: 37 additions & 32 deletions

Dashboard/Services/DatabaseService.NocHealth.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Threading.Tasks;
1213
using Microsoft.Data.SqlClient;
1314
using PerformanceMonitorDashboard.Helpers;
@@ -631,7 +632,7 @@ ORDER BY r.total_elapsed_time DESC
631632
{
632633
using var cmd = new SqlCommand(query, connection);
633634
cmd.CommandTimeout = 10;
634-
cmd.Parameters.AddWithValue("@thresholdMs", (long)thresholdMinutes * 60 * 1000);
635+
cmd.Parameters.Add(new SqlParameter("@thresholdMs", SqlDbType.BigInt) { Value = (long)thresholdMinutes * 60 * 1000 });
635636
using var reader = await cmd.ExecuteReaderAsync();
636637

637638
while (await reader.ReadAsync())
@@ -685,7 +686,7 @@ ORDER BY percent_of_average DESC
685686
{
686687
using var cmd = new SqlCommand(query, connection);
687688
cmd.CommandTimeout = 10;
688-
cmd.Parameters.AddWithValue("@thresholdPercent", thresholdPercent);
689+
cmd.Parameters.Add(new SqlParameter("@thresholdPercent", SqlDbType.Int) { Value = thresholdPercent });
689690
using var reader = await cmd.ExecuteReaderAsync();
690691

691692
while (await reader.ReadAsync())

Dashboard/Services/DatabaseService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Globalization;
1213
using System.Threading;
1314
using System.Threading.Tasks;
@@ -302,10 +303,10 @@ public async Task UpdateCollectorScheduleAsync(int scheduleId, bool enabled, int
302303

303304
using var command = new SqlCommand(query, connection);
304305
command.CommandTimeout = 120;
305-
command.Parameters.AddWithValue("@schedule_id", scheduleId);
306-
command.Parameters.AddWithValue("@enabled", enabled);
307-
command.Parameters.AddWithValue("@frequency_minutes", frequencyMinutes);
308-
command.Parameters.AddWithValue("@retention_days", retentionDays);
306+
command.Parameters.Add(new SqlParameter("@schedule_id", SqlDbType.Int) { Value = scheduleId });
307+
command.Parameters.Add(new SqlParameter("@enabled", SqlDbType.Bit) { Value = enabled });
308+
command.Parameters.Add(new SqlParameter("@frequency_minutes", SqlDbType.Int) { Value = frequencyMinutes });
309+
command.Parameters.Add(new SqlParameter("@retention_days", SqlDbType.Int) { Value = retentionDays });
309310

310311
await command.ExecuteNonQueryAsync();
311312
}

Installer/Program.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.IO;
1213
using System.Linq;
1314
using System.Net.Http;
@@ -1341,16 +1342,16 @@ INSERT INTO PerformanceMonitor.config.installation_history
13411342

13421343
using (var insertCmd = new SqlCommand(insertSql, connection))
13431344
{
1344-
insertCmd.Parameters.AddWithValue("@installer_version", assemblyVersion);
1345-
insertCmd.Parameters.AddWithValue("@installer_info_version", (object?)infoVersion ?? DBNull.Value);
1346-
insertCmd.Parameters.AddWithValue("@sql_server_version", sqlVersion);
1347-
insertCmd.Parameters.AddWithValue("@sql_server_edition", sqlEdition);
1348-
insertCmd.Parameters.AddWithValue("@installation_type", installationType);
1349-
insertCmd.Parameters.AddWithValue("@previous_version", (object?)previousVersion ?? DBNull.Value);
1350-
insertCmd.Parameters.AddWithValue("@installation_status", status);
1351-
insertCmd.Parameters.AddWithValue("@files_executed", filesExecuted);
1352-
insertCmd.Parameters.AddWithValue("@files_failed", filesFailed);
1353-
insertCmd.Parameters.AddWithValue("@installation_duration_ms", durationMs);
1345+
insertCmd.Parameters.Add(new SqlParameter("@installer_version", SqlDbType.NVarChar, 50) { Value = assemblyVersion });
1346+
insertCmd.Parameters.Add(new SqlParameter("@installer_info_version", SqlDbType.NVarChar, 100) { Value = (object?)infoVersion ?? DBNull.Value });
1347+
insertCmd.Parameters.Add(new SqlParameter("@sql_server_version", SqlDbType.NVarChar, 500) { Value = sqlVersion });
1348+
insertCmd.Parameters.Add(new SqlParameter("@sql_server_edition", SqlDbType.NVarChar, 128) { Value = sqlEdition });
1349+
insertCmd.Parameters.Add(new SqlParameter("@installation_type", SqlDbType.VarChar, 20) { Value = installationType });
1350+
insertCmd.Parameters.Add(new SqlParameter("@previous_version", SqlDbType.NVarChar, 50) { Value = (object?)previousVersion ?? DBNull.Value });
1351+
insertCmd.Parameters.Add(new SqlParameter("@installation_status", SqlDbType.VarChar, 20) { Value = status });
1352+
insertCmd.Parameters.Add(new SqlParameter("@files_executed", SqlDbType.Int) { Value = filesExecuted });
1353+
insertCmd.Parameters.Add(new SqlParameter("@files_failed", SqlDbType.Int) { Value = filesFailed });
1354+
insertCmd.Parameters.Add(new SqlParameter("@installation_duration_ms", SqlDbType.BigInt) { Value = durationMs });
13541355

13551356
await insertCmd.ExecuteNonQueryAsync().ConfigureAwait(false);
13561357
}

Lite/Controls/ServerTab.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ Include the latest event timestamp so acknowledgement is only
536536
DateTime? latestEventTime = null;
537537
if (blockingCount > 0 || deadlockCount > 0)
538538
{
539-
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.CollectionTime);
540-
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.CollectionTime);
539+
var latestBlocking = blockedProcessTask.Result.Max(r => (DateTime?)r.EventTime);
540+
var latestDeadlock = deadlockTask.Result.Max(r => (DateTime?)r.DeadlockTime);
541541
latestEventTime = latestBlocking > latestDeadlock ? latestBlocking : latestDeadlock;
542542
}
543543
AlertCountsChanged?.Invoke(blockingCount, deadlockCount, latestEventTime);

Lite/Services/LocalDataService.Overview.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ FROM blocked_process_reports
8585
SELECT COUNT(*)
8686
FROM deadlocks
8787
WHERE server_id = $1
88-
AND collection_time >= $2";
88+
AND deadlock_time >= $2";
8989
cmd.Parameters.Add(new DuckDBParameter { Value = serverId });
9090
cmd.Parameters.Add(new DuckDBParameter { Value = DateTime.UtcNow.AddHours(-1) });
9191
var result = await cmd.ExecuteScalarAsync();

Lite/Services/LocalDataService.QueryStats.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Threading.Tasks;
1213
using DuckDB.NET.Data;
1314
using Microsoft.Data.SqlClient;
@@ -27,7 +28,7 @@ public partial class LocalDataService
2728
quoted_name = QUOTENAME(d.name)
2829
FROM sys.databases AS d
2930
WHERE d.name = @database_name;", connection);
30-
command.Parameters.AddWithValue("@database_name", databaseName);
31+
command.Parameters.Add(new SqlParameter("@database_name", SqlDbType.NVarChar, 128) { Value = databaseName });
3132
var result = await command.ExecuteScalarAsync();
3233
return result as string;
3334
}
@@ -247,7 +248,7 @@ qs.total_elapsed_time DESC
247248
using var connection = new SqlConnection(connectionString);
248249
await connection.OpenAsync();
249250
using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
250-
command.Parameters.AddWithValue("@query_hash", queryHash);
251+
command.Parameters.Add(new SqlParameter("@query_hash", SqlDbType.VarChar, 64) { Value = queryHash });
251252
var result = await command.ExecuteScalarAsync();
252253
return result as string;
253254
}
@@ -294,8 +295,8 @@ ps.total_elapsed_time DESC
294295
@schema_name;";
295296

296297
using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
297-
command.Parameters.AddWithValue("@object_name", objectName);
298-
command.Parameters.AddWithValue("@schema_name", schemaName);
298+
command.Parameters.Add(new SqlParameter("@object_name", SqlDbType.NVarChar, 128) { Value = objectName });
299+
command.Parameters.Add(new SqlParameter("@schema_name", SqlDbType.NVarChar, 128) { Value = schemaName });
299300
var result = await command.ExecuteScalarAsync();
300301
return result as string;
301302
}

Lite/Services/LocalDataService.QueryStore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
using System;
1010
using System.Collections.Generic;
11+
using System.Data;
1112
using System.Threading.Tasks;
1213
using DuckDB.NET.Data;
1314
using Microsoft.Data.SqlClient;
@@ -198,7 +199,7 @@ AND qsp.query_plan IS NOT NULL
198199
@plan_id;";
199200

200201
using var command = new SqlCommand(query, connection) { CommandTimeout = 30 };
201-
command.Parameters.AddWithValue("@plan_id", planId);
202+
command.Parameters.Add(new SqlParameter("@plan_id", SqlDbType.BigInt) { Value = planId });
202203
var result = await command.ExecuteScalarAsync();
203204
return result as string;
204205
}

Lite/Services/RemoteCollectorService.BlockedProcessReport.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
using System;
10+
using System.Data;
1011
using System.Diagnostics;
1112
using System.Threading;
1213
using System.Threading.Tasks;
@@ -126,7 +127,7 @@ LEFT JOIN sys.dm_xe_sessions AS dxs
126127
WHERE ses.name = @session_name;", connection))
127128
{
128129
cmd.CommandTimeout = CommandTimeoutSeconds;
129-
cmd.Parameters.AddWithValue("@session_name", BlockedProcessXeSessionName);
130+
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = BlockedProcessXeSessionName });
130131
var result = await cmd.ExecuteScalarAsync(cancellationToken);
131132

132133
if (result != null)
@@ -205,7 +206,7 @@ FROM sys.database_event_sessions AS des
205206
WHERE des.name = @session_name;", connection))
206207
{
207208
cmd.CommandTimeout = CommandTimeoutSeconds;
208-
cmd.Parameters.AddWithValue("@session_name", BlockedProcessXeSessionName);
209+
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = BlockedProcessXeSessionName });
209210
var result = await cmd.ExecuteScalarAsync(cancellationToken);
210211

211212
if (result != null)
@@ -380,8 +381,7 @@ as it lingers in the ring buffer across collection cycles. */
380381
command.CommandTimeout = CommandTimeoutSeconds;
381382

382383
/* Use the most recent timestamp from DuckDB as the cutoff, or fall back to 10-minute window */
383-
command.Parameters.AddWithValue("@cutoff_time",
384-
lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10));
384+
command.Parameters.Add(new SqlParameter("@cutoff_time", SqlDbType.DateTime2) { Value = lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10) });
385385

386386
try
387387
{

Lite/Services/RemoteCollectorService.Deadlocks.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
using System;
10+
using System.Data;
1011
using System.Diagnostics;
1112
using System.Linq;
1213
using System.Threading;
@@ -80,7 +81,7 @@ LEFT JOIN sys.dm_xe_sessions AS dxs
8081
WHERE ses.name = @session_name;", connection))
8182
{
8283
cmd.CommandTimeout = CommandTimeoutSeconds;
83-
cmd.Parameters.AddWithValue("@session_name", DeadlockXeSessionName);
84+
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = DeadlockXeSessionName });
8485
var result = await cmd.ExecuteScalarAsync(cancellationToken);
8586

8687
if (result != null)
@@ -179,7 +180,7 @@ ELSE NULL
179180
END;", connection))
180181
{
181182
cmd.CommandTimeout = CommandTimeoutSeconds;
182-
cmd.Parameters.AddWithValue("@session_name", DeadlockXeSessionName);
183+
cmd.Parameters.Add(new SqlParameter("@session_name", SqlDbType.NVarChar, 128) { Value = DeadlockXeSessionName });
183184
var result = await cmd.ExecuteScalarAsync(cancellationToken);
184185

185186
if (result is int hasCorrectEvent)
@@ -380,8 +381,7 @@ lingers in the ring buffer across collection cycles. */
380381
command.CommandTimeout = CommandTimeoutSeconds;
381382

382383
/* Use the most recent timestamp from DuckDB as the cutoff, or fall back to 10-minute window */
383-
command.Parameters.AddWithValue("@cutoff_time",
384-
lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10));
384+
command.Parameters.Add(new SqlParameter("@cutoff_time", SqlDbType.DateTime2) { Value = lastCollectedTime ?? DateTime.UtcNow.AddMinutes(-10) });
385385

386386
try
387387
{

0 commit comments

Comments
 (0)