I finally get it. It's not just the code I use to execute the ExecuteScalar method but it is mainly the code up stream that is executing the class. It is everything calling your code. That said, can someone please see if the code executing the my SQL class has faults. I still cant pass the scans. First I will show you two examples of the code calling my code, then the calling code, and finally the executing code, which I formulated and displayed from a previous post.
Calling code with three parameters:
public bool isTamAsp(int aspKey, int fy, string accountCode)
{
MyParam myParam;
string sqlQuery = "select isTamMacom = count(macom_key) FROM hier_fy " +
"WHERE hier_key = #aspKey AND fy = #fy AND #accountCode NOT IN (3,4,7,8) AND macom_key IN (select hier_key from lkup_e581_MacomThatRequireTAM) AND is_visible = 1 AND is_active = 1";
QueryContainer Instance = new QueryContainer(sqlQuery);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#aspKey", Instance.AddParameterType(_DbTypes.Int));
myParam.SqlParam.Value = aspKey;
Instance.parameterList.Add(myParam);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#fy", Instance.AddParameterType(_DbTypes.Int));
myParam.SqlParam.Value = fy;
Instance.parameterList.Add(myParam);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#accountCode", Instance.AddParameterType(_DbTypes._string));
myParam.SqlParam.Value = accountCode;
Instance.parameterList.Add(myParam);
if (Convert.ToInt32(ExecuteScaler(Instance)) < 1)
return false;
return true;
}
Calling code with no parameters:
public long GetMarinesUploadNextUploadKey()
{
string query = "SELECT MAX(upload_key) FROM temp_auth_usmc_upload";
QueryContainer Instance = new QueryContainer(query);
string result = Convert.ToString(ExecuteScaler(Instance));
if (string.IsNullOrEmpty(result))
return 1;
else
return Convert.ToInt64(result) + 1;
}
Code calling my previous code with three parameters:
public bool isTamAsp(int aspKey, int fy, string accountCode)
{
return e581provider.isTamAsp(aspKey, fy, accountCode);
}
Method calling the SQL executing my code:
DbCommand command = _provider.CreateCommand();
command.Connection = _connection;
{
command.CommandText = Instance.Query;
command.CommandType = CommandType.Text;
if (Instance.parameterList.Count > 0)
{
foreach (var p in Instance.parameterList)
{
command.Parameters.Add(p.SqlParam);
}
}
if (_useTransaction) { command.Transaction = _transaction; }
try
{
returnValue = command.ExecuteScalar();
}
My Class containing the SQL string and the cmd parameter List
public enum _DbTypes
{
Int = 1, _string = 2, _long = 3, _bool = 4, _DateTime = 5,
_decimal = 6, _float = 7, _short = 8, _bite = 9
}
public class MyParam
{
public SqlParameter SqlParam { get; set; }
}
/// <summary>
/// Summary description for QueryContainer SGH
/// </summary>
public class QueryContainer
{
string _query;
public List<MyParam> parameterList = new List<MyParam>();
public QueryContainer(string query) { _query = query; }
public SqlDbType AddParameterType(_DbTypes id)
{
switch (id)
{
case _DbTypes.Int:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "int", true);
case _DbTypes._string:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "NVarChar", true);
case _DbTypes._long:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "SqlDbType.BigInt", true);
case _DbTypes._bool:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "SqlDbType.Bit", true);
}
return SqlDbType.VarChar;
}
public string Query
{
get
{
return _query;
}
set { _query = value; }
}
}
I don't see a vulnerability in that code, but I have an idea what the scan may be asking for. The problem could be that this code makes it too easy for developers to ignore the parameterList collection in your class. If I'm a new developer in your organization who hasn't discovered Sql injection yet, I'd be tempted to ignore all that complicated query parameter stuff and just use string concatenation before setting your Query property.
Instead of wrapping this in a class, what I'm more used to seeing is a single method that has a signature like this:
IEnumerable<T> GetData<T>(string query, IEnumerable<Sqlparameter> parameters)
...or some permutation of that method signature that may use arrays or lists instead of IEnumerable. This forces downstream developers to deal with that parameters argument to the method. They can't ignore it, and so the temptation to use a quick, lazy string concatenation call to substitute some user-provided data into the query is reduced.
Related
I have a bit of code (using dapper) that is executing a query against my database (ASE 12). The sql has a where clause like this where r.reg_id = #reg_id
Now in the database the reg_id column is a type of numeric, length 6, Prec 18, Scale 0
In the code the #reg_id variable is a Int32 and I would expect an exception to occur when I call the async method query but it just never errors out...
QUESTION: Is there a setting/configuration I need to set in dapper to get datatype errors to kick out exceptions?
Code below will work because I set the datatype to decimal, but for other team members I would like for an exception to get kicked out so they know what the issue is... Any ideas?
public async Task<IEnumerable<CentersForRegIdResponse>> Handle(CentersForRegIdQuery request, CancellationToken cancellationToken)
{
StringBuilder sql = new StringBuilder();
sql.Append(" SELECT c.ctr_id, RTRIM(LTRIM(c.ctr_shname)) AS ctr_shname, RTRIM(LTRIM(r.reg_name)) AS reg_name ");
sql.Append(" FROM center c, ");
sql.Append(" region r ");
sql.Append(" WHERE c.jcc_active = 'Y' and c.reg_id = r.reg_id and r.reg_id = #reg_id ");
sql.Append(" ORDER BY c.ctr_shname ");
string query = sql.ToString();
// custom mapping
_dapperTools.DapperCustomMapping<CentersForRegIdResponse>();
try
{
using (IDbConnection dbConnection = _dapperTools._aseconnection)
{
// example get list of rows by args
var arguments = new
{
#reg_id = request.RegId
};
IEnumerable<CentersForRegIdResponse> allRows =
await dbConnection.QueryAsync<CentersForRegIdResponse>(query, arguments);
return allRows;
}
}
catch (Exception ex)
{
_dapperTools.ReportSqlError(_log, query, ex);
return null;
}
}
public class CentersForRegIdQuery : IRequest<IEnumerable<CentersForRegIdResponse>>
{
public Decimal RegId { get; set; }
}
I'm trying to create a GetScalar method. We use a lot of DataTables in the legacy systems. I'm converting them to EF.
Here's the goal. This is a simple method that takes a Stored Procedure name and parameters, then returns the First Row, First Column of the result set.
public object GetScalar(string command, CommandType type = CommandType.StoredProcedure, List<SqlParameter> parameterList = null)
{
try
{
using (var cnn = new SqlConnection(ConnectionString))
{
using (var cmd = new SqlCommand(command, cnn))
{
cmd.CommandType = type;
if (parameterList != null)
{
foreach (var p in parameterList)
{
cmd.Parameters.Add(p);
}
}
cmd.Connection.Open();
object obj = cmd.ExecuteScalar();
cmd.Connection.Close();
cmd.Parameters.Clear();
return obj;
}
}
}
catch (Exception ex)
{
Logging.LogError(ex, "MSSqlUtility.GetScalar");
return -1;
}
}
I'd like to have a similar method in EF. Here's what I have so far, but this returns all columns - not just the first.
protected T SelectScalar<T>(string inStoredProcedure, ICollection<SqlParameter> inParameters = null)
{
T result = default(T);
if (inParameters != null && inParameters.Count > 0)
{
string paramNames = string.Join(",", inParameters.Select(parameter => parameter.ParameterName).ToList());
string sqlString = inStoredProcedure + " " + paramNames;
object[] paramValues = inParameters.Cast<object>().ToArray();
result = Database.SqlQuery<T>(sqlString, paramValues).FirstOrDefault();
}
else
{
result = Database.SqlQuery<T>(inStoredProcedure).FirstOrDefault();
}
return result;
}
Example usage. This returns a string object - "John Doe".
public string GetUserName(string employeeID)
{
if (string.IsNullOrWhiteSpace(employeeID))
{
return string.Empty;
}
var parameters = new Collection<SqlParameter>();
parameters.Add(StoredProcedureParameterBuilder.StringParam("#EmployeeID", employeeID, 20));
return this.SelectScalar<string>("dbo.GetUserName", parameters).Trim();
}
The SQL query looks something like this:
SELECT
FirstName + ' ' + Last Name
,EmployeeID
FROM Users
Changing the stored procedure isn't an option - we need both columns in other contexts. The current code returns both the name and the ID. I'd like to fetch just the first column, first row, of whatever my query may spit back.
Ended up having to do a bit more manual work. If anyone has a better solution, I'm all ears, but this works for my use case:
/// <summary>
/// Selects the first item in the query's result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="inStoredProcedure">The in stored procedure.</param>
/// <param name="inParameters">The in parameters.</param>
/// <returns>The first object in the specified type T.</returns>
protected T SelectScalar<T>(string inStoredProcedure, ICollection<SqlParameter> inParameters = null)
{
using (System.Data.IDbCommand command = Database.Connection.CreateCommand())
{
try
{
command.CommandText = inStoredProcedure;
command.CommandTimeout = command.Connection.ConnectionTimeout;
if(inParameters != null && inParameters.Count > 0)
{
string paramNames = string.Join(",", inParameters.Select(parameter => parameter.ParameterName).ToList());
command.CommandText += " " + paramNames;
foreach (var param in inParameters)
{
command.Parameters.Add(param);
}
}
Database.Connection.Open();
return (T)command.ExecuteScalar();
}
finally
{
Database.Connection.Close();
command.Parameters.Clear();
}
}
}
I am using MySql 5.6x with Visual Studio 2015, windows 10, 64-bit. C# as programming language. In my CRUD.cs (Class file) i have created the following method:
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters[p].Value = v;
}
}
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
I am passing the query and Parameters List like this:
protected void loginBtn_Click(object sender, EventArgs e)
{
string sql = "SELECT * FROM dept_login WHERE (user_email = ?user_email OR user_cell = ?user_cell) AND userkey = ?userkey";
string[] param = new string[] {
"?user_email,"+ userid.Text.ToString(),
"?user_cell,"+ userid.Text.ToString(),
"?userkey,"+ userkey.Text.ToString()
};
if (db.dbQuery(sql, param))
{
msg.Text = "Ok";
}
else
{
msg.Text = "<strong class='text-danger'>Authentication Failed</strong>";
}
}
Now the problem is that after the loop iteration complete, it directly jumps to the catch() Block and generate an Exception that:
Parameter '?user_email' not found in the collection.
Am i doing this correct to send params like that? is there any other way to do the same?
Thanks
EDIT: I think the best way might be the two-dimensional array to collect the parameters and their values and loop then within the method to fetch the parameters in cmd.AddWidthValues()? I may be wrong...
In your dbQuery you don't create the parameters collection with the expected names, so you get the error when you try to set a value for a parameter that doesn't exist
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters.AddWithValue(p, v);
}
}
if (cmd.ExecuteNonQuery() > 0)
flag = true;
}
catch (Exception exc)
{
error(exc);
}
}
Of course this will add every parameter with a datatype equals to a string and thus is very prone to errors if your datatable columns are not of string type
A better approach would be this one
List<MySqlParameter> parameters = new List<MySqlParameter>()
{
{new MySqlParameter()
{
ParameterName = "?user_mail",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?user_cell",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?userkey",
MySqlDbType = MySqlDbType.VarChar,
Value = userkey.Text
},
}
if (db.dbQuery(sql, parameters))
....
and in dbQuery receive the list adding it to the parameters collection
public bool dbQuery(string sql, List<MySqlParameter> paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null)
cmd.Parameters.AddRange(paramList.ToArray());
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
By the way, unrelated to your actual problem, but your code doesn't seem to close and dispose the connection. This will lead to very nasty problems to diagnose and fix. Try to use the using statement and avoid a global connection variable
EDIT
As you have noticed the ExecuteNonQuery doesn't work with a SELECT statement, you need to use ExecuteReader and check if you get some return value
using(MySqlDataReader reader = cmd.ExecuteReader())
{
flag = reader.HasRows;
}
This, of course, means that you will get troubles when you want to insert, update or delete record where instead you need the ExecuteNonQuery. Creating a general purpose function to handle different kind of query is very difficult and doesn't worth the work and debug required. Better use some kind of well know ORM software like EntityFramework or Dapper.
Your SQL Commands' Parameters collection does not contain those parameters, so you cannot index them in this manner:
cmd.Parameters[p].Value = v;
You need to add them to the Commands' Parameters collection in this manner: cmd.Parameters.AddWithValue(p, v);.
I'm facing a pretty weird construct. The Foo type returned in an IEnumerable loses its data as soon as the enumeration ends. This means that I can't do a enumeration.First() because the data would be lost right away.
A loop over it works, but since I know it will contain only a single element that would be weird.
int Test(out int something)
IEnumerable<Foo> enumeration = ...
for (var foo in enumeration) {
something = foo.GetSomething ();
return foo.GetAnInt ();
}
something = 42;
return 0;
}
Another way I though of is abusing a Linq Select, but that's just as horrible.
Is there a way to work around this limitation? Fixing the root cause is obviously superior, but difficult in this case.
Edit: It's an IEnumerable<IDataRecord> that is yield returned from a transactioned SQL data reader.
public IEnumerable<IDataRecord> ExecuteReader (SqlCommand cmd)
{
using (var con = GetConnection()) {
con.Open ();
using (var tr = con.BeginTransaction ()) {
cmd.Connection = con;
var reader = cmd.ExecuteReader ();
while (reader.Read ()) {
yield return reader;
}
tr.Commit ();
}
}
}
The problem is that your ExecuteReader method does simply return the SqlDataReader itself (which implements IDataRecord), instead of returning a block of data. So when you do this:
var list = ExecuteReader(...).ToList();
In that case all elements of the list will be the same SqlDataReader instance, but after the ToList has been executed, the reader has been closed. I'm a bit surprised that you don't get an ObjectDisposedException.
For this to work, you need to return a copy of the data in the IDataRecord. You think you can iterate the elements in the data record. An other option is to change the ExecuteReader to the following:
public IEnumerable<T> ExecuteReader<T>(SqlCommand cmd,
Func<IDataRecord, T> recordCreator)
{
using (var con = GetConnection()) {
con.Open ();
using (var tr = con.BeginTransaction()) {
cmd.Connection = con;
var reader = cmd.ExecuteReader();
while (reader.Read()) {
yield return recordCreator(reader);
}
tr.Commit();
}
}
}
This way you can do the following:
var list = ExecuteReader(command, record => new
{
Item1 = record.GetInt("id"),
Item2 = record.GetString("name")
});
Note: I'm not sure why you need a transaction for this anyway.
How about
int Test(out int something)
{
IEnumerable<Foo> enumeration = ...
var values = enumeration
.Select(foo => new
{
something = foo.GetSomething(),
anInt = foo.GetAnInt()
})
.FirstOrDefault();
if (values != null)
{
something = values.something;
return values.anInt;
}
else
{
something = 42;
return 0;
}
}
GetSomething and GetAnInt are called while inside the enumeration.
Another idea could be to convert the result type of the method from IEnumerable to IEnumerator. That way, scope control is much easier, and returning single results does not require any (fake) loop, too.
Edit: I think I found a way to refactor the whole issue. This circumvents the initial problem by using new disposable class that contains the logic formerly found in the method. It's very readable and less code even.
public TransactedConnection GetConnection (string text)
{
return new TransactedConnection (_ConnectionString, text);
}
public class TransactedConnection : IDisposable
{
private readonly SQLiteCommand _Cmd;
private readonly SQLiteConnection _Con;
private readonly SQLiteTransaction _Tr;
public TransactedConnection (string connection, string text)
{
_Cmd = new SQLiteCommand (text);
_Con = new SQLiteConnection (connection);
_Con.Open ();
_Cmd.Connection = _Con;
_Tr = _Con.BeginTransaction ();
}
public void Dispose ()
{
_Tr.Commit ();
_Tr.Dispose ();
_Con.Dispose ();
_Cmd.Dispose ();
}
public SQLiteParameterCollection Parameters
{
get
{
return _Cmd.Parameters;
}
}
public int ExecuteNonQuery ()
{
return _Cmd.ExecuteNonQuery ();
}
public object ExecuteScalar ()
{
return _Cmd.ExecuteScalar ();
}
public SQLiteDataReader ExecuteReader ()
{
return _Cmd.ExecuteReader ();
}
}
public void Test (string match)
{
var text = "SELECT * FROM Data WHERE foo=#bar;";
using (var cmd = GetConnection (text)) {
cmd.Parameters.Add ("#bar", DbType.String).Value = match;
using (var reader = cmd.ExecuteReader ()) {
while (reader.Read ()) {
Console.WriteLine (reader["foo"]);
}
}
}
}
Is there a way to limit the number of entries WMI retrieves with a WQL statement?
I say this because running a query to retrieve all Win32_NTLogEvent instances is taking forever! All I really need are the most recent events (for about a week, or 2000 entries)
Here's a snippet of the code I'm using to get the log data. Other queries such as Win32_Processor are nice and quick.
if (Configuration.OnlyErrorLogs)
{
// If Information logs should be suppressed, only get events where event type is not 3
WMIDataTemp1 = DataRetriever.GetWMIData("Win32_NTLogEvent", "EventType<>3");
}
else
{
WMIDataTemp1 = DataRetriever.GetWMIData("Win32_NTLogEvent");
}
foreach (ManagementObject Object in WMIDataTemp1)
{
this.Log.Add(new Log(Object));
}
And the functions to get WMI data are as follows:
public static ManagementObject[] GetWMIData(string wmiClass) { return GetWMIData(wmiClass, "", "CIMV2"); }
public static ManagementObject[] GetWMIData(string wmiClass, string whereClause) { return GetWMIData(wmiClass, whereClause, "CIMV2"); }
public static ManagementObject[] GetWMIData(string wmiClass, string whereClause, string nameSpace)
{
try
{
// If a where clause has been set, prepare the clause to add to the query string
if (whereClause != "")
{
whereClause = " WHERE " + whereClause;
}
// Create a search query
string query = "SELECT * FROM " + wmiClass + whereClause;
ManagementObjectSearcher wmiSearcher = new ManagementObjectSearcher("root\\" + nameSpace, query);
ManagementObjectCollection matches = wmiSearcher.Get();
// Create an array to hold the matches
ManagementObject[] matchArray = new ManagementObject[matches.Count];
// If matches found, copy to output
if(matches.Count > 0)
{
// Copy the search matches into this array
matches.CopyTo(matchArray, 0);
}
// Return array
return matchArray;
}
catch (Exception e)
{
ErrorDialogue errorReporter = new ErrorDialogue(e);
return null;
}
}
Where each Log gets stored:
public class Log
{
public string Category = "N/A";
public string DateTime = "N/A";
public UInt16 ID = 0;
public string Level = "N/A";
public string Message = "N/A";
public string Source = "N/A";
public Log() { }
public Log(ManagementObject wmiLogEvent)
{
this.GetInfo(wmiLogEvent);
}
public void GetInfo(ManagementObject wmiLogEvent)
{
try
{
this.Category = DataRetriever.GetValue(wmiLogEvent, "CategoryString");
this.DateTime = DataRetriever.GetValue(wmiLogEvent, "TimeGenerated");
this.ID = DataRetriever.GetValueUInt16(wmiLogEvent, "EventIdentifier");
this.Level = DataRetriever.ConvertEventType(DataRetriever.GetValueUInt16(wmiLogEvent, "CategoryString"));
this.Message = DataRetriever.GetValue(wmiLogEvent, "Message");
this.Source = DataRetriever.GetValue(wmiLogEvent, "SourceName");
}
catch (Exception e)
{
ErrorDialogue errorReporter = new ErrorDialogue(e);
}
}
}
One option is to use a WHERE clause to specify the range of the entries you want...
For example you could use TimeGenerated in the WHERE clause to specify a time-based range...
Another option is to set BlockSize accordingly when creating ManagementObjectSearcher.
You could use that to specify that you want 2000 entries per call for example - together with an ORDER BY TimeGenerated DESC this should give a nice result.
Speed is not a strong suit for WMI. It tends to be quite memory intensive. However, the question has been addressed and there are a few things you can do. Check out Why are my queries taking such a long time to complete? from Microsoft TechNet.
Now using the System.Diagnostics.EventLog class as a faster alternative. Much more beneficial to the program compared to WMI.
http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlog.aspx