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();
}
}
}
Related
Is it possible to call api with params object[] as parameter
[HttpGet("{procName}/{parametarList}")]
public ActionResult<string> Get(string procName , params object[] parametarList)
{
string JSONString = string.Empty;
using (var ado = new ADO())
{
var ds = ado.ExecuteAndReturnDS("execute " + procName " #0, #1, #2,#3,#4,#5", parametarList);
JSONString = JsonConvert.SerializeObject(ds.Tables[0]);
return new JsonResult(JSONString);
}
}
public DataSet ExecuteAndReturnDS(string upit, params object[] parametri)
{
try
{
_Conn();
command = new SqlCommand(upit, _Conn);
command.CommandTimeout = 200;
_ds = new DataSet();
_sqlda = new SqlDataAdapter(command);
for (int i = 0; i <= parametri.Count() - 1; i++)
{
if (parametri[i] == null)
{
command.Parameters.AddWithValue("#" + i, DBNull.Value);
}
else
{
command.Parameters.AddWithValue("#" + i, parametri[i]);
}
}
_sqlda.Fill(_ds);
if (_Conn.State == ConnectionState.Open)
{
_sqlda.Dispose();
_Conn.Dispose();
_Conn.Close();
}
return _ds;
}
catch (Exception ex)
{
return null;
}
}
Like this
/api/values/myProcedure/param1/param2/param3/param4/etc/
I can see 2 ways you can do it.
The first one is to pass in the parameterlist as just a string and then split it into an array of objects in the method.
Here is an example of how you can easily split a string in C#: https://learn.microsoft.com/en-us/dotnet/csharp/how-to/parse-strings-using-split
The second is to use a POST request instead of a GET request, this would allow you to pass in more complicated objects.
I've a problem and I can't figured it out how to solve it.
I've a class for fetching data from a Database, in this class I've a method for a simple select * this method is called
List<T> All<T>(string tableName)
and you have to specify which resource you want to fetch, for example
All<User>("users")
And, aside from the classic SQL Reader and SQL Command, the core of the method is this
public override List<T> All<T>(string resource)
{
List<T> result = new List<T>();
using (MySqlConnection sqlConnection = new MySqlConnection(connectionString))
{
sqlConnection.Open();
try
{
string query = "SELECT * FROM " + resource + " WHERE 1=1";
using (MySqlCommand sqlCommand = new MySqlCommand(query, sqlConnection))
{
lock (locker)
{
MySqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
T model = Activator.CreateInstance<T>();
Dictionary<string, object> _properties = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
string property = reader.GetName(i);
object value = reader.GetValue(i);
_properties.Add(property, value);
}
var type = model.GetType();
var method = type.GetMethod("SetProperties");
var invoked = method.Invoke(model, new object[] { _properties });
result.Add(model);
}
}
reader.Close();
}
}
}
catch (Exception ex)
{
Program.eventLogger.Add(new Event(EventType.Error, "SQL Data Providers", "Exception catched on All", ex));
}
finally
{
sqlConnection.Close();
}
}
return result;
}
Basically, based on the Type from the method header, the method will try to create an new instance of the specific type, later for each field from the query, it will fills all the attributes of the class on a temporaneous list. Once it's done it will try to call the method "SetProperties" which basically set every attributes of the class using reflection.
This is the core of SetProperties, equal for each entity:
public virtual bool SetProperties(Dictionary<string,object> properties)
{
if(this.ValidateData(properties))
{
FillNullableAttributes(properties);
// Iterate trough every key : value pairs in properties
foreach (KeyValuePair<string, object> kvp in properties)
{
if (this.data.Contains(kvp.Key))
{
var property = this.GetType().GetProperty(kvp.Key);
PropertyInfo propertyInfo = this.GetType().GetProperty(kvp.Key);
// Set the current fetched key with the given value if !null
if (kvp.Value != null)
{
Type fetchedType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
object safeConversion = (kvp.Value == null || kvp.Value == DBNull.Value) ? null : Convert.ChangeType(kvp.Value, fetchedType);
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, safeConversion, null);
}
}
}
}
return true;
}
return false;
}
In conclusion the result, which is a list, will be returned and the specific Entity will have its own BindingList filled. The binding list, for each entity is described as follow:
public static BindingList<Seller> items = new BindingList<Seller>();
This code works fine, even if there's a lot of space for improvements I know, but if I called it twice like this:
User.items = new BindingList<User>(provider.All<User>("users"));
User.items = new BindingList<User>(provider.All<User>("users"));
The second list will be filled by empty entities, the counting of the will be correct but they will be empties... and that shouldn't occurs.
The only thing that I figured it out, from the debugging, is that on the second call
var invoked = method.Invoke(model, new object[] { _properties });
invoked is set to false.
The result of:
var invoked = method.Invoke(model, new object[] { _properties });
Is the return value from your SetProperties method, not whether the method was invoked as your question indicates. Your SetProperties method is telling you that it was unable to do its work, debug that and you will find your answer.
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've an issue with some sqlite code, it seems that something in the function below is causing the database to remain open after it ought to have been closed by the using statement, I can work round the issue with a call to GC.collect() in the calling function, but I'd like to try and work out the cause of the issue. I've tried adding a call to GC.Collect() after the using statment but this doesn't seem to be enough, asm assuming that something is blocking the GC from clearing up?
/// <summary>
/// Gets the Points of Interest for a given Category
/// </summary>
/// <param name="rootPath">Target Path</param>
/// <param name="category">Category to search</param>
/// <returns>A collection of Points of interest</returns>
public static Collection<PointOfInterest> GetPointsOfInterest(string rootPath, PointOfInterestCategory category)
{
if (category == null)
{
throw new ArgumentNullException("category");
}
Collection<PointOfInterest> pointsOfInterest = new Collection<PointOfInterest>();
string databaseLocation = string.Format(Resources.DataFilePath, rootPath, "poidata.db");
using (SQLiteConnection connection = new SQLiteConnection(string.Format("Data Source={0};Version=3;", databaseLocation)))
{
connection.Open();
using (SQLiteCommand command = new SQLiteCommand(
"SELECT latmin + (latmax - latmin / 2) as lat, lonmin + (lonmax - lonmin / 2) as lon, city, street, housenr, name FROM poicoord JOIN poidata ON poicoord.poiid = poidata.poiid JOIN poiname on poiname.docid = poicoord.poiid WHERE type = #category",
connection))
{
command.Parameters.Add(new SQLiteParameter("category", DbType.Int32) { Value = category.Id });
SQLiteDataReader reader = command.ExecuteReader();
int latOrdinal = reader.GetOrdinal("lat");
int lonOrdinal = reader.GetOrdinal("lon");
int nameOrdinal = reader.GetOrdinal("name");
int houseNrOrdinal = reader.GetOrdinal("housenr");
int streetOrdinal = reader.GetOrdinal("street");
int cityOrdinal = reader.GetOrdinal("city");
while (reader.Read())
{
pointsOfInterest.Add(new PointOfInterest()
{
Latitude = reader.GetDouble(latOrdinal),
Longitude = reader.GetDouble(lonOrdinal),
Name = reader.GetString(nameOrdinal),
HouseNumber = reader.IsDBNull(houseNrOrdinal) ? string.Empty : reader.GetString(houseNrOrdinal),
Street = reader.IsDBNull(streetOrdinal) ? string.Empty : reader.GetString(streetOrdinal),
City = reader.IsDBNull(cityOrdinal) ? string.Empty : reader.GetString(cityOrdinal),
});
}
}
}
return pointsOfInterest;
}
Try implementing a using statement instead of just assigning the reader to a variable:
using (SQLiteDataReader reader=command.ExecuteReader()) {
...
}
or close the reader manually:
reader.Close();
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.