Backend/e-suite.API/e-suite.Database.Migrator/SqlWrapper/SqlWrapper.cs
2026-01-20 21:50:10 +00:00

313 lines
9.8 KiB
C#

using Microsoft.Data.SqlClient;
using System.Data;
using System.Security;
using System.Xml.Linq;
using System.Xml;
namespace e_suite.Database.Migrator.SqlWrapper;
public class SqlWrapper : ISqlWrapper
{
/// <summary>
/// contains the details of parameter including the value. Settings the value to null or DBNull.Value will result in a DBNull.Value being passed with the stored procedure call.
/// </summary>
private struct SqlParam
{
public string Name;
public SqlDbType Type;
public ParameterDirection Direction;
public object Value;
}
private const string ReturnValueParamName = "_ReturnValue";
private readonly IDbConnection _sqlConn;
private readonly List<SqlParam> _sqlParams;
private string _commandName = string.Empty;
private string _commandText = string.Empty;
public string CommandName
{
get => _commandName;
set
{
if (_commandName != value)
{
_commandName = value;
if (!string.IsNullOrEmpty(_commandText))
{
_commandText = string.Empty;
}
}
}
}
public string CommandText
{
get => _commandText;
set
{
if (_commandText != value)
{
_commandText = value;
if (!string.IsNullOrEmpty(_commandName))
{
_commandName = string.Empty;
}
}
}
}
/// <summary>
/// Creates an instance of the SqlCommand. Wraps setting everything needed to make a stored procedure call based on the parameters supplied.
/// </summary>
/// <returns>Prepared SqlCommand object ready for execution.</returns>
/// FxCop warning has been deliberately suppressed as the CommandText is only allowed to be a stored procedure name.
/// This is ensured by the hard coding of the CommandType.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
private SqlCommand PrepareSqlCommand()
{
SqlCommand sqlCommand;
SqlCommand? tempSqlCommand = null;
try
{
tempSqlCommand = new SqlCommand
{
CommandTimeout = Timeout,
Connection = _sqlConn as SqlConnection
};
if (!string.IsNullOrEmpty(CommandName))
{
tempSqlCommand.CommandType = CommandType.StoredProcedure;
tempSqlCommand.CommandText = CommandName;
}
else if (!string.IsNullOrEmpty(CommandText))
{
tempSqlCommand.CommandType = CommandType.Text;
tempSqlCommand.CommandText = CommandText;
}
foreach (var sqlParam in _sqlParams)
{
if (sqlParam.Value is SecureString sqlParamValue)
{
tempSqlCommand.Parameters.AddSecure(sqlParam.Name, sqlParamValue);
}
else
{
var sqlParameter = tempSqlCommand.Parameters.Add(sqlParam.Name, sqlParam.Type);
if (sqlParam.Type == SqlDbType.VarChar)
{
sqlParameter.Size = -1;
}
sqlParameter.Direction = sqlParam.Direction;
sqlParameter.Value = sqlParam.Value;
}
}
sqlCommand = tempSqlCommand;
}
catch
{
tempSqlCommand?.Dispose();
throw;
}
return sqlCommand;
}
/// <summary>
/// retrieves the output parameters putting each into their respective SqlParam contained in the sqlParams list.
/// </summary>
/// <param name="sqlCommand">the SqlCommand object after execution so that the output parameters may be retrieved.</param>
private void RetrieveOutputParams(SqlCommand sqlCommand)
{
ParameterDirection[] outParamDirections = { ParameterDirection.InputOutput, ParameterDirection.Output, ParameterDirection.ReturnValue };
foreach (SqlParameter sqlParameter in sqlCommand.Parameters)
{
if (outParamDirections.Contains(sqlParameter.Direction))
{
for (int i = 0; i < _sqlParams.Count; i++)
{
SqlParam sqlParam = _sqlParams[i];
if (sqlParam.Name == sqlParameter.ParameterName)
{
sqlParam.Value = sqlParameter.Value;
_sqlParams[i] = sqlParam; //will this be needed?
break;
}
}
}
}
}
/// <summary>
/// class constructor. this will create and immediately open the database connection.
/// </summary>
public SqlWrapper(string connectionString) //constructor
{
_sqlConn = new SqlConnection(connectionString);
_sqlConn.Open();
_sqlParams = new List<SqlParam>();
}
public SqlWrapper(IDbConnection sqlConnection) //constructor
{
_sqlConn = sqlConnection;
_sqlConn.Open();
_sqlParams = new List<SqlParam>();
}
/// <summary>
/// Overrideable Dispose method. This will be called when the object is destroyed, either by ending a uses clause or by the garbage collector.
/// </summary>
/// <param name="disposing">states that this was called by the garbage collector</param>
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
_sqlConn.Close();
}
}
IsDisposed = true;
}
/// <summary>
/// object destructor, will call the overridable Dispose method.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public bool IsDisposed { get; private set; }
/// <summary>
/// Removes all parameters from the internal parameter list.
/// </summary>
public void Clear()
{
_sqlParams.Clear();
}
public int ParamCount => _sqlParams.Count;
public int Timeout { get; set; } = 30;
public void AddParameter(string name, SqlDbType type, object value)
{
AddParameter(name, type, ParameterDirection.Input, value);
}
/// <summary>
/// Adds a new parameter to the stored procedure
/// </summary>
/// <param name="name">name of the parameter. For example @test the @ symbol is optional</param>
/// <param name="type">database type of the parameter</param>
/// <param name="direction">Specifies if this is an input or output parameter</param>
/// <param name="value">actual value for the parameter</param>
public void AddParameter(string name, SqlDbType type, ParameterDirection direction, object? value)
{
if (value == null)
{
value = DBNull.Value;
}
SqlParam sqlParam = new SqlParam
{
Name = name,
Type = type,
Direction = direction,
Value = value
};
_sqlParams.Add(sqlParam);
}
/// <summary>
/// Retrieves the current value of a parameter
/// </summary>
/// <param name="name">name of the parameter that you need the value for</param>
/// <returns>either the value of the named parameter, or null if it does not exist</returns>
public object? GetParamValue(string name)
{
foreach (var sqlParam in _sqlParams)
{
if (sqlParam.Name == name)
{
return sqlParam.Value;
}
}
return null;
}
/// <summary>
/// Retrieves the procedures return value
/// </summary>
/// <returns>the return value from the procedure call</returns>
public object? GetReturnValue()
{
return GetParamValue(ReturnValueParamName);
}
/// <summary>
/// Execute the stored procedure without the expectation of any result sets being returned.
/// note: output parameters will be set when the method completes.
/// </summary>
public void Execute()
{
AddParameter(ReturnValueParamName, SqlDbType.Int, ParameterDirection.ReturnValue, null);
using (SqlCommand sqlCommand = PrepareSqlCommand())
{
sqlCommand.ExecuteNonQuery();
RetrieveOutputParams(sqlCommand);
}
}
public XmlReader ExecuteXmlReader()
{
using (SqlCommand sqlCommand = PrepareSqlCommand())
{
return sqlCommand.ExecuteXmlReader();
}
}
/// <summary>
/// Execute the stored procedure with the expection of an XML Stream result set.
/// </summary>
/// <returns>XML document containing the result of the stored procedure call</returns>
public XmlDocument ExecuteXmlDocument()
{
using (XmlReader xReader = ExecuteXmlReader())
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(xReader);
return xmlDocument;
}
}
public XDocument ExecuteXDocument()
{
using (XmlReader xReader = ExecuteXmlReader())
{
XDocument xmlDocument = XDocument.Load(xReader);
return xmlDocument;
}
}
public IDataReader ExecuteReader()
{
using (SqlCommand sqlCommand = PrepareSqlCommand())
{
return sqlCommand.ExecuteReader();
}
}
}