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(); } } }