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
{
///
/// 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.
///
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 _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;
}
}
}
}
///
/// Creates an instance of the SqlCommand. Wraps setting everything needed to make a stored procedure call based on the parameters supplied.
///
/// Prepared SqlCommand object ready for execution.
/// 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;
}
///
/// retrieves the output parameters putting each into their respective SqlParam contained in the sqlParams list.
///
/// the SqlCommand object after execution so that the output parameters may be retrieved.
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;
}
}
}
}
}
///
/// class constructor. this will create and immediately open the database connection.
///
public SqlWrapper(string connectionString) //constructor
{
_sqlConn = new SqlConnection(connectionString);
_sqlConn.Open();
_sqlParams = new List();
}
public SqlWrapper(IDbConnection sqlConnection) //constructor
{
_sqlConn = sqlConnection;
_sqlConn.Open();
_sqlParams = new List();
}
///
/// Overrideable Dispose method. This will be called when the object is destroyed, either by ending a uses clause or by the garbage collector.
///
/// states that this was called by the garbage collector
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
_sqlConn.Close();
}
}
IsDisposed = true;
}
///
/// object destructor, will call the overridable Dispose method.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public bool IsDisposed { get; private set; }
///
/// Removes all parameters from the internal parameter list.
///
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);
}
///
/// Adds a new parameter to the stored procedure
///
/// name of the parameter. For example @test the @ symbol is optional
/// database type of the parameter
/// Specifies if this is an input or output parameter
/// actual value for the parameter
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);
}
///
/// Retrieves the current value of a parameter
///
/// name of the parameter that you need the value for
/// either the value of the named parameter, or null if it does not exist
public object? GetParamValue(string name)
{
foreach (var sqlParam in _sqlParams)
{
if (sqlParam.Name == name)
{
return sqlParam.Value;
}
}
return null;
}
///
/// Retrieves the procedures return value
///
/// the return value from the procedure call
public object? GetReturnValue()
{
return GetParamValue(ReturnValueParamName);
}
///
/// Execute the stored procedure without the expectation of any result sets being returned.
/// note: output parameters will be set when the method completes.
///
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();
}
}
///
/// Execute the stored procedure with the expection of an XML Stream result set.
///
/// XML document containing the result of the stored procedure call
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();
}
}
}