using System.Data;
using System.Data.Common;
using Microsoft.Extensions.Logging;
namespace Bit.Seeder.Migration.Utils;
///
/// Extension methods for database operations with improved null safety.
///
public static class DbExtensions
{
///
/// Executes a scalar query and returns the result with proper null handling.
/// Safely handles null and DBNull.Value results without throwing exceptions.
///
/// The expected return type
/// The database command to execute
/// The default value to return if result is null/DBNull
/// Optional logger for warnings
/// Optional context for logging (e.g., "table count for Users")
/// The scalar value cast to type T, or defaultValue if null/DBNull
public static T GetScalarValue(
this DbCommand command,
T defaultValue = default!,
ILogger? logger = null,
string? context = null)
{
var result = command.ExecuteScalar();
if (result == null || result == DBNull.Value)
{
if (logger != null && !string.IsNullOrEmpty(context))
{
logger.LogDebug("Query returned null for {Context}, using default value", context);
}
return defaultValue;
}
try
{
// Handle direct cast if types match
if (result is T typedResult)
{
return typedResult;
}
// Handle conversion for compatible types
return (T)Convert.ChangeType(result, typeof(T));
}
catch (InvalidCastException ex)
{
if (logger != null)
{
logger.LogWarning(
"Could not cast result to {TargetType} for {Context}. Result type: {ActualType}. Error: {Error}",
typeof(T).Name,
context ?? "query",
result.GetType().Name,
ex.Message
);
}
return defaultValue;
}
catch (FormatException ex)
{
if (logger != null)
{
logger.LogWarning(
"Format error converting result to {TargetType} for {Context}. Value: {Value}. Error: {Error}",
typeof(T).Name,
context ?? "query",
result,
ex.Message
);
}
return defaultValue;
}
}
///
/// Safely attempts to rollback a transaction, catching and logging any errors.
/// This prevents rollback errors from masking the original exception.
///
/// The transaction to rollback
/// The database connection (used to check if still open)
/// Logger for warnings
/// Context for logging (e.g., table name)
public static void SafeRollback(
this DbTransaction transaction,
DbConnection? connection,
ILogger logger,
string? context = null)
{
try
{
if (connection?.State == ConnectionState.Open)
{
transaction.Rollback();
if (!string.IsNullOrEmpty(context))
{
logger.LogDebug("Transaction rolled back for {Context}", context);
}
}
else
{
logger.LogWarning(
"Could not rollback transaction for {Context}: connection is {State}",
context ?? "operation",
connection?.State.ToString() ?? "null"
);
}
}
catch (Exception rollbackEx)
{
logger.LogWarning(
rollbackEx,
"Error during transaction rollback for {Context}: {Message}",
context ?? "operation",
rollbackEx.Message
);
}
}
}