Stored Procedure Parameter with AddWithValue() - c#

I am trying to loop thru the elements of an Array[] for Parameter names and Object[] for object values using the AddWithValue().
Unfortunately it is saying "Procedure or function 'sp_add_Request' expects parameter '#RequestType', which was not supplied". When I run to cursor, I can see all the parameters are supplied, I do not understand where the problem is. Please help. See code below:
object[] myValues = new Object[] { txtID.Text, ddlAmissionType.Text };
string[] paramsNames = new string[] { "#CHI", "#RequestType"};
dbConn.addData("sp_add_Request", paramsNames, myValues, lbMsg.Text);
Parent method:
public static bool addData(string storedProcName, string[] dynamicParamName, object[] aramVals, string msg)
{
for (int i = 0; i < dynamicParamName.Length; i++)
{
cmd2.Parameters.AddWithValue(dynamicParamName[i], paramVals[i]);
//cmd2.Parameters.Add(dynamicParamName[i], dynamicParamValues[i]);
try
{
if (cmd2.Connection.State == ConnectionState.Closed)
{
cmd2.Connection.Open();
int stat = cmd2.ExecuteNonQuery();
if (stat > 0)
{
res = true;
msg = "Recorded Added successfully";
cmd2.Connection.Close();
cmd2.Dispose();
}
}
}
}

You are accessing the database in your loop because the command is executed within the for loop. So you're performing your command before the 2nd parameter is added. Move the try block outside the for loop and you should be fine.
public static bool addData(string storedProcName, string[] dynamicParamName, object[] paramVals, string msg)
{
for (int i = 0; i < dynamicParamName.Length; i++)
{
cmd2.Parameters.AddWithValue(dynamicParamName[i], paramVals[i]);
//cmd2.Parameters.Add(dynamicParamName[i], dynamicParamValues[i]);
}
try
{
if (cmd2.Connection.State == ConnectionState.Closed)
{
cmd2.Connection.Open();
}
int stat = cmd2.ExecuteNonQuery();
if (stat > 0)
{
res = true;
msg = "Recorded Added successfully";
cmd2.Connection.Close();
cmd2.Dispose();
}
}
}
You may want to just go ahead and put your connection object in a using statement that way it's automatically disposed. #abatishchev's answer below shows the proper way to handle your ado objects.

Create, use and dispose new connection/command objects each time method is being called. This will use connection pooling and other performance positive techniques.
public static bool addData(string storedProcName, string[] dynamicParamName, object[] paramVals, string msg)
{
SqlParameter[] paramArr = new SqlParameter[dynamicParamName.Length];
for(int i = 0; i < dynamicParamName.Length; i++)
{
paramArr[i] = new SqlParameter(dynamicParamName[i], paramVals[i]);
}
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = commandText;
//command.CommandType = CommandType.StoredProcedure ; // if needed
command.Parameters.AddRange(paramArr);
connection.Open();
return command.ExecuteNonQuery() > 0;
}
}
See MSDN: SqlParameterCollection.AddRange() method.
Also you can use LINQ:
SqlParameter[] paramArr = dynamicParamName
.Select((paramName,i) => new SqlParameter(paramName, paramVals[i]).ToArray();
or
SqlParameter[] paramArr = Enumerable.Range(0, dynamicParamName.Length - 1)
.Select(i => new SqlParameter(dynamicParamName[i], paramVals[i])
.ToArray();

Related

No increase in value c# [duplicate]

This is a basic c# application but I am quite rusty. I am just going to start with showing you my code
using System;
using System.Data.SqlClient;
using System.Text;
namespace DatabaseAdder
{
class Program
{
static void Main(string[] args)
{
int RUId = 0;
int QuestionId = 0;
DateTime Date = DateTime.Now;
string QuestionWhenAnswered ;
string QuestionResponse;
int Accepted;
string AssignedWorkStation;
string CompleteToken;
try
{for (int i = 0; i < 300; i++) {
QuestionId ++;
QuestionIncrementInSetsOfTwelve(QuestionId);
Console.WriteLine(i );
Console.WriteLine( QuestionId);
Random rand = new Random();
// Build connection string
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "localhost"; // update me
builder.UserID = "sa"; // update me
builder.Password = "Mypassword123"; // update me
builder.InitialCatalog = "CDA";
// Connect to SQL
Console.Write("Connecting to SQL Server ... ");
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
connection.Open();
var sql = "INSERT INTO QuestionResponses(RUId, QuestionId,Date,QuestionWhenAnswered,QuestionResponse,Accepted,AssignedWorkStation,CompleteToken)" +
" VALUES(#RUId, #QuestionId,#Date,#QuestionWhenAnswered,#QuestionResponse,#Accepted,#AssignedWorkStation,#CompleteToken)";
using (var cmd = new SqlCommand(sql, connection))
{
cmd.Parameters.AddWithValue("#RUId", "1" );
cmd.Parameters.AddWithValue("#QuestionId", "1");
cmd.Parameters.AddWithValue("#Date", DateTime.Now);
cmd.Parameters.AddWithValue("#QuestionWhenAnswered", "sam");
cmd.Parameters.AddWithValue("#QuestionResponse", "sam");
cmd.Parameters.AddWithValue("#Accepted", "1");
cmd.Parameters.AddWithValue("#AssignedWorkStation", "sam");
cmd.Parameters.AddWithValue("#CompleteToken", "sam");
cmd.ExecuteNonQuery();
}
}
}
}
catch (SqlException e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("All done. Press any key to finish...");
Console.ReadKey(true);
}
static int QuestionIncrementInSetsOfTwelve(int questionId)
{
if(questionId < 12)
{
questionId = 0;
}
else
{
}
return questionId;
}
}
}
The questionincrementinsetsoftwelve is not changing the value when it is called even when I have debugged and can watch that its value is over 12 but it is still not setting this back to 0.
I understand that there is probably something very small I am overlooking so be easy on my ego.
This bit
QuestionId ++;
QuestionIncrementInSetsOfTwelve(QuestionId);
Should be
QuestionId ++;
QuestionId = QuestionIncrementInSetsOfTwelve(QuestionId);
integers are passed by value, not by reference. So a new integer that has the same value as QuestionId is being passed to the method, and that new integer is being returned from it. You have to assign the result. The behavior you are looking for can be achieved by using the ref keyword but isn't needed so long as we assign the result.
You're passing QuestionIncrementInSetsOfTwelve() a value. It's not updating the parameter, it's taking it in, doing "stuff," and returning a new value.
If you want to alter the parameter passed in, use the ref keyword.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref
Example from microsoft:
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Notice how this returns nothing? It updates the argument passed in, because it was passed by ref.

C# MySQLDataReader returns column names instead of field values

I am using MySQLClient with a local database. I wrote a method which returns a list of data about the user, where I specify the columns I want the data from and it generates the query dynamically.
However, the reader is only returning the column names rather than the actual data and I don't know why, since the same method works previously in the program when the user is logging in.
I am using parameterised queries to protect from SQL injection.
Here is my code. I have removed parts which are unrelated to the problem, but i can give full code if needed.
namespace Library_application
{
class MainProgram
{
public static Int32 user_id;
static void Main()
{
MySqlConnection conn = LoginProgram.Start();
//this is the login process and works perfectly fine so i won't show its code
if (conn != null)
{
//this is where things start to break
NewUser(conn);
}
Console.ReadLine();
}
static void NewUser(MySqlConnection conn)
{
//three types of users, currently only using student
string query = "SELECT user_role FROM Users WHERE user_id=#user_id";
Dictionary<string, string> vars = new Dictionary<string, string>
{
["#user_id"] = user_id.ToString()
};
MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);
if (reader.Read())
{
string user_role = reader["user_role"].ToString();
reader.Close();
//this works fine and it correctly identifies the role and creates a student
Student user = new Student(conn, user_id);
//later i will add the logic to detect and create the other users but i just need this to work first
}
else
{
throw new Exception($"no user_role for user_id - {user_id}");
}
}
}
class SQLControler
{
public static MySqlDataReader SqlQuery(MySqlConnection conn, string query, Dictionary<string, string> vars, int type)
{
MySqlCommand cmd = new MySqlCommand(query, conn);
int count = vars.Count();
MySqlParameter[] param = new MySqlParameter[count];
//adds the parameters to the command
for (int i = 0; i < count; i++)
{
string key = vars.ElementAt(i).Key;
param[i] = new MySqlParameter(key, vars[key]);
cmd.Parameters.Add(param[i]);
}
//runs this one
if (type == 0)
{
Console.WriteLine("------------------------------------");
return cmd.ExecuteReader();
//returns the reader so i can get the data later and keep this reusable
}
else if (type == 1)
{
cmd.ExecuteNonQuery();
return null;
}
else
{
throw new Exception("incorrect type value");
}
}
}
class User
{
public List<string> GetValues(MySqlConnection conn, List<string> vals, int user_id)
{
Dictionary<string, string> vars = new Dictionary<string, string> { };
//------------------------------------------------------------------------------------
//this section is generating the query and parameters
//using parameters to protect against sql injection, i know that it ins't essential in this scenario
//but it will be later, so if i fix it by simply removing the parameterisation then im just kicking the problem down the road
string args = "";
for (int i = 0; i < vals.Count(); i++)
{
args = args + "#" + vals[i];
vars.Add("#" + vals[i], vals[i]);
if ((i + 1) != vals.Count())
{
args = args + ", ";
}
}
string query = "SELECT " + args + " FROM Users WHERE user_id = #user_id";
Console.WriteLine(query);
vars.Add("#user_id", user_id.ToString());
//-------------------------------------------------------------------------------------
//sends the connection, query, parameters, and query type (0 means i use a reader (select), 1 means i use non query (delete etc..))
MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);
List<string> return_vals = new List<string>();
if (reader.Read())
{
//loops through the reader and adds the value to list
for (int i = 0; i < vals.Count(); i++)
{
//vals is a list of column names in the ame order they will be returned
//i think this is where it's breaking but im not certain
return_vals.Add(reader[vals[i]].ToString());
}
reader.Close();
return return_vals;
}
else
{
throw new Exception("no data");
}
}
}
class Student : User
{
public Student(MySqlConnection conn, int user_id)
{
Console.WriteLine("student created");
//list of the data i want to retrieve from the db
//must be the column names
List<string> vals = new List<string> { "user_forename", "user_surname", "user_role", "user_status"};
//should return a list with the values in the specified columns from the user with the matching id
List<string> return_vals = base.GetValues(conn, vals, user_id);
//for some reason i am getting back the column names rather than the values in the fields
foreach(var v in return_vals)
{
Console.WriteLine(v);
}
}
}
What i have tried:
- Using getstring
- Using index rather than column names
- Specifying a specific column name
- Using while (reader.Read)
- Requesting different number of columns
I have used this method during the login section and it works perfectly there (code below). I can't figure out why it doesnt work here (code above) aswell.
static Boolean Login(MySqlConnection conn)
{
Console.Write("Username: ");
string username = Console.ReadLine();
Console.Write("Password: ");
string password = Console.ReadLine();
string query = "SELECT user_id, username, password FROM Users WHERE username=#username";
Dictionary<string, string> vars = new Dictionary<string, string>
{
["#username"] = username
};
MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);
Boolean valid_login = ValidLogin(reader, password);
return (valid_login);
}
static Boolean ValidLogin(MySqlDataReader reader, string password)
{
Boolean return_val;
if (reader.Read())
{
//currently just returns the password as is, I will implement the hashing later
password = PasswordHash(password);
if (password == reader["password"].ToString())
{
MainProgram.user_id = Convert.ToInt32(reader["user_id"]);
return_val = true;
}
else
{
return_val = false;
}
}
else
{
return_val = false;
}
reader.Close();
return return_val;
}
The problem is here:
string args = "";
for (int i = 0; i < vals.Count(); i++)
{
args = args + "#" + vals[i];
vars.Add("#" + vals[i], vals[i]);
// ...
}
string query = "SELECT " + args + " FROM Users WHERE user_id = #user_id";
This builds a query that looks like:
SELECT #user_forename, #user_surname, #user_role, #user_status FROM Users WHERE user_id = #user_id;
Meanwhile, vars.Add("#" + vals[i], vals[i]); ends up mapping #user_forename to "user_forename" in the MySqlParameterCollection for the query. Your query ends up selecting the (constant) value of those parameters for each row in the database.
The solution is:
Don't prepend # to the column names you're selecting.
Don't add the column names as variables to the query.
You can do this by replacing that whole loop with:
string args = string.Join(", ", vals);

Having a hard time adding type <T> to list

I need help with my code as I can't seem to find the culprit of why adding type <T> to a list many times overwrites the other values being added.
Below is my code:
public IEnumerable<T> Query<T>(string query, object parameters = null) where T : new()
{
var lists = new List<T>();
var ObjType = new T();
var ObjProps = ObjType.GetType().GetProperties();
int Objlen = ObjProps.Length;
if (parameters == null)
{
DBConnection.Open();
using (MySqlTransaction mysqlTransaction = DBConnection.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
{
try
{
using (MySqlCommand command = new MySqlCommand(query, DBConnection))
{
command.Transaction = mysqlTransaction;
using (MySqlDataReader dataReader = command.ExecuteReader())
{
if (dataReader.HasRows)
{
while (dataReader.Read())
{
for (int i = 0; i < dataReader.FieldCount; i++)
{
var dataValue = dataReader.GetValue(i);
ObjProps.FirstOrDefault(p => p.Name == dataReader.GetName(i)).SetValue(ObjType, dataValue);
}
lists.Add(ObjType);
}
}
}
}
}
catch (Exception)
{
mysqlTransaction.Rollback();
DBConnection.Close();
throw;
}
finally
{
if (DBConnection != null)
{
DBConnection.Close();
}
}
}
}
return lists;
}
I can't seem to find the logic error in my code. As I execute the code, the first add to lists are okay.
But as soon as it iterates again for the for loop, the values seemed to be overwritten and I don't even have a clue as to why this is happening.
Below is the picture:
Any help/clarification/answer is greatly appreciated.
The line var ObjType = new T(); should be inside the while loop and not at the top. Otherwise you are just modifying the same object and not adding new ones.
You aren't creating a new <T> (ObjType) in your for loop. When you do, don't forget to reset ObjProps to reference the new ObjType.
You get the error because you are always setting the properties of the same instance and adding the same instance to the list.
Add ObjType = new T(); before the for loop.

how can i use delegate to fill 2D list?

i have the following Function
public string StoredProcedure(string StoredProcedureName,List<List<string>> StoredProcedureParameter,string ReturnValue = null)
{
string functionReturnValue;
cmd = new SqlCommand(StoredProcedureName, conniction(true));
cmd.CommandType = CommandType.StoredProcedure;
for (int i = 0; i <= StoredProcedureParameter.Count; i++)
{
cmd.Parameters.AddWithValue(StoredProcedureParameter[0][i], StoredProcedureParameter[1][i]);
}
int exec_cmd = (int)cmd.ExecuteScalar();
if (ReturnValue != null)
{
SqlParameter prm = new SqlParameter(ReturnValue, SqlDbType.Int);
cmd.Parameters.Add(prm).Direction = ParameterDirection.ReturnValue;
functionReturnValue = prm.ToString();
}
else
{
functionReturnValue = exec_cmd.ToString();
}
return functionReturnValue;
}
and i need to pass 2D list Parameter to StoredProcedureParameter using delegate and maybe lambda expression
Do the following.
Change the signature of your method to accept delegates:
public string StoredProcedure(string StoredProcedureName, Func<List<List<string>>> StoredProcedureParameterFunc, string ReturnValue = null)
{
List<List<string>> StoredProcedureParameter = StoredProcedureParameterFunc();
... // your original code here
}
Invoke the method like this:
var result = StoredProcedure("SPName", GetLists, "returnID");
Where GetLists is a new method returning a "2D" list of strings, e.g.:
private List<List<string>> GetLists() {
var result = new List<List<string>>();
... // fill result here
return result;
}

Problems with for loop in C# ADO net code

I have some serous issues with this code. I have the method GetWeatherItemData that takes a parameter name and a period as input for getting data from a SQL database and it seems to work fine.
But I want to create a method that can do the same work for multiple parameters, so I created a method named GetSelectedWeatherItemsData that takes an array of parameters as input and loops through my first method, but for some reason it will only return data for 1 parameter only, the first in the array of input parameters.
Here is the first method:
public CustomDataType GetWeatherItemData(string parameterName, string fromTime, string toTime)
{
/* This method takes parameter name, start time and end time as input it will then return
* all the measurement values and their timestamp as array for the specific parameter
*/
CustomDataType getWeatherItemObj = new CustomDataType();
List<double> valueList = new List<double>();
List<string> timeStampList = new List<string>();
List<int> parameterIdList = new List<int>();
List<string> ParameterNameList = new List<string>();
try
{
using (conn = new SqlConnection(connectionString))// create and open a connection object
{
// 1. create a command object identifying the stored procedure
cmd = new SqlCommand("GetWeatherItemData", conn);
// 2.Let the command object know we will execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;
// 3. add the 3 parameters to command, so the can be passed to the stored procedure
cmd.Parameters.Add("#WeatherParameterName", SqlDbType.VarChar).Value = parameterName;
cmd.Parameters.Add("#FromTime", SqlDbType.VarChar).Value = fromTime;
cmd.Parameters.Add("#ToTime", SqlDbType.VarChar).Value = toTime;
//open connection
conn.Open();
// execute the command
reader = cmd.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
valueList.Add((double)reader["MeasurementValue"]);
timeStampList.Add(reader["MeasurementDateTime"].ToString());
parameterIdList.Add((int)reader["WeatherParameterID"]);
}
}
//close connection
reader.Close();
//changed to arrays to support webservices
getWeatherItemObj.arrayOfValue = valueList.ToArray();
getWeatherItemObj.arrayOfTimestamp = timeStampList.ToArray();
getWeatherItemObj.arrayOfParameterID = parameterIdList.ToArray();
for (counter = 0; counter < getWeatherItemObj.arrayOfValue.Length; counter++)
{
ParameterNameList.Add(GetParameterInfo(parameterName).ParameterName);
}
getWeatherItemObj.arrayOfParameterName = ParameterNameList.ToArray();
}
}
catch (SqlException e)
{
Console.WriteLine("Connection failed");
Console.WriteLine(e.Message);
Thread.Sleep(5000);
}
return getWeatherItemObj;
}
Here is the code I have problem with. It takes an array of parameter names, and period as input. But it only returns data for the first element in the input array, as if it does the for loop only once and jumps out.I tested the code inside the for loop by assigning them a fixed number like parameterName[3] instead of parameterName[counter] and this resulted in that I got data for that element not the first element. So for some reason the for loops does only 1 iteration.
public CustomDataType GetSelectedWeatherItemsData(string[] parameterName, string fromTime, string toTime)
{
CustomDataType tempObj;
List<double> valueList = new List<double>();
List<string> timeStampList = new List<string>();
List<int> paramIdStampList = new List<int>();
List<string> ParameterNameList = new List<string>();
for (counter = 0; counter < (parameterName.Length); counter++)
{
tempObj = GetWeatherItemData(parameterName[counter], fromTime, toTime);
valueList.AddRange(GetWeatherItemData(parameterName[counter], fromTime, toTime).arrayOfValue);
timeStampList.AddRange(GetWeatherItemData(parameterName[counter], fromTime, toTime).arrayOfTimestamp);
//paramIdStampList.AddRange(tempObj.arrayOfParameterID);
ParameterNameList.AddRange(GetWeatherItemData(parameterName[counter], fromTime, toTime).arrayOfParameterName);
}
getSelectedItemsObj = new CustomDataType();
getSelectedItemsObj.arrayOfValue = valueList.ToArray();
getSelectedItemsObj.arrayOfTimestamp = timeStampList.ToArray();
//getSelectedItemsObj.arrayOfParameterID = paramIdStampList.ToArray();
getSelectedItemsObj.arrayOfParameterName = ParameterNameList.ToArray();
return getSelectedItemsObj;
}
What seems definitely wrong is the fact you're calling the simple web service four times inside your loop - why don't you just call it once and then use the results you get back?
Something like:
for (counter = 0; counter < (parameterName.Length); counter++)
{
tempObj = GetWeatherItemData(parameterName[counter], fromTime, toTime);
valueList.AddRange(tempObj.arrayOfValue);
timeStampList.AddRange(tempObj.arrayOfTimestamp);
//paramIdStampList.AddRange(tempObj.arrayOfParameterID);
ParameterNameList.AddRange(tempObj.arrayOfParameterName);
}

Categories