Array index out of bounds error C# - c#

i have this code that im getting error and, and cant understand why im getting the error. the code is as following: im using two arrays a string and a double(data values with their corresponding tinestamp). But for some reason im getting error that: index out of bounds error on this line:
getSelectedItemsObj.arrayOfTimeStamp = GetItemData(parameterName[counter], fromTime, toTime).arrayOfTimeStamp;
If i remove this line and only use arrayOfValue the code works fine, but i need both
.......... Thanks for the fast replies, here is how GetItemData is set up
public CustomDataType GetItemData(string parameterName, string fromTime, string toTime)
{
getWeatherItemObj = new CustomDataType();
// get the parameter ID
prameterID = GetParameterInfo(parameterName).ParameterID;
//get the nr of items to size value arrays
tableSize = GetTableSize(parameterName, fromTime, toTime, prameterID);
getWeatherItemObj.arrayOfValue = new double[tableSize];
getWeatherItemObj.arrayOfTimeStamp = new string[tableSize];
counter = 0;
try
{
using (conn = new SqlConnection(connectionString))// create and open a connection object
{
// 1. create a command object identifying the stored procedure
cmd = new SqlCommand("GetItemData", 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("#ParameterName", 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())
{
getWeatherItemObj.arrayOfValue[counter] = (double)reader["MeasurementValue"];
getWeatherItemObj.arrayOfTimeStamp[counter] = reader["MeasurementDateTime"].ToString();
counter++;
}
}
//close connection
reader.Close();
}
}
catch (SqlException e)
{
Console.WriteLine("Connection failed");
Console.WriteLine(e.Message);
Thread.Sleep(5000);
}
return getWeatherItemObj;
}
class CustomDataType
{
public double[] arrayOfValue;
public string[] arrayOfTimeStamp;
}
public CustomDataType GetSelectedtemsData(string[] parameterName, string fromTime, string toTime)
{
numberOfParameters = parameterName.Length;//Get the number of given parameters
tableSize = 0;
for (counter = 0; counter < numberOfParameters; counter++)
{
tableSize = tableSize + GetItemData(parameterName[counter], fromTime, toTime).arrayOfTimeStamp.Length;
}
getSelectedItemsObj = new CustomDataType();
getSelectedItemsObj.arrayOfValue = new double[tableSize];
getSelectedItemsObj.arrayOfTimeStamp = new string[tableSize];
for (counter = 0; counter < tableSize; counter++)
{
getSelectedItemsObj.arrayOfValue = GetItemData(parameterName[counter], fromTime, toTime).arrayOfValue;
getSelectedItemsObj.arrayOfTimeStamp = GetItemData(parameterName[counter], fromTime, toTime).arrayOfTimeStamp;
}
return getSelectedItemsObj;
}

The problem is right here:
for (counter = 0; counter < tableSize; counter++)
{
getSelectedItemsObj.arrayOfValue = GetItemData(parameterName[counter], fromTime, toTime).arrayOfValue;
getSelectedItemsObj.arrayOfTimeStamp = GetItemData(parameterName[counter], fromTime, toTime).arrayOfTimeStamp;
}
Your counter size is being incremented based on tableSize, but its being used as if its based on the size of parameterName. Nothing in your code enforces this assumption, so as soon as tableSize is larger than parameterName, you will get the error you are seeing.

The problem is in how you're treating your counter. If you remove the timestamp stuff, the counter never really gets modified so it all appears to work, but if you have the timestamp stuff in there, then your for loop initializer looks like this:
for (counter = 0; counter < tableSize; counter++)
where tableSize is ultimately greater than parameterName[].Length. Since later you're calling parameterName[counter], counter will be out of bounds. You need to make sure that you're never attempting to access something in parameterName[] by an index that is not legal within that array.
Edit:
Another possibility, if removing only that line removes the error, is that the return of
GetItemData(parameterName[counter], fromTime, toTime).arrayOfTimeStamp;
is larger than the specified array allocated in
getSelectedItemsObj.arrayOfTimeStamp = new string[tableSize];
In order to make such a determination, you would need to post the code to that method as well.

Please try the following:
public CustomDataType GetSelectedtemsData(string[] parameterName, string fromTime, string toTime)
{
CustomDataType tempObj; // *Rename this variable*
List<double> valueList = new List<double>();
List<string> timeStampList = new List<string>();
for (counter = 0; counter < parameterName.Length; counter++)
{
tempObj = GetItemData(parameterName[counter], fromTime, toTime);
valueList.AddRange(tempObj.arrayOfValue);
timeStampList.AddRange(tempObj.arrayOfTimeStamp);
}
getSelectedItemsObj = new CustomDataType();
getSelectedItemsObj.arrayOfValue = valueList.ToArray();
getSelectedItemsObj.arrayOfTimeStamp = timeStampList.ToArray();
return getSelectedItemsObj;
}
I was going to try it your way, but once I realized you were pulling each parameterName 3 times, and the second loop would need an inner loop (since you would need to iterate through each index in each GetItemData), and that my fix would have resulted in multiple copies of the same data anyways; I decided to rebuild the method using two List.
If this does not solve your issue then I will need the complete error message, the stack trace from the error message, and the code of the top most method on the error stack.
Note: There are a number of additional optimizations which may help you like:
You may also want to update GetItemData to also use List instead of arrays so you can skip the tableSize = GetTableSize(parameterName, fromTime, toTime, prameterID); line.
You may want to change CustomDataType so arrayOfValue and arrayOfTimeStamp are not arrays. Then, instead of passing a CustomDataType out you could pass a CustomDataType[] or even List<CustomDataType>.

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.

Testing DB content, Zooming in on 1 value

I have a SQLite database that returns 1 number to
Select value from Income where symbol = "AE" and statementitem = "Revenues" and periodtype = "Annual" and yearmonth = "Dec 2019"; --1811.2
I use a bit of c# code to test this to make sure nothing is missed:
public string GetIncome(string dbFile, string symbol, string aq, string yearmonth)
{
var answer = String.Empty;
try
{
using (var con = new SQLiteConnection($"URI=file:{dbFile}"))
{
con.Open();
using var cmd = new SQLiteCommand(con)
{
CommandText = $"Select value from Income where symbol = '{symbol}' and statementitem = 'Revenues' and periodtype = '{aq}' and yearmonth = '{yearmonth}';"
};
using SQLiteDataReader dataReader = cmd.ExecuteReader();
while (dataReader.Read())
{
answer = dataReader.GetString(1);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
return answer;
}
This errors out with System.IndexOutOfRangeException : Index was outside the bounds of the array.
Whats the best way to pick up on that value please?
See official docs:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlite.sqlitedatareader.getstring?view=msdata-sqlite-3.1.0#Microsoft_Data_Sqlite_SqliteDataReader_GetString_System_Int32_
It tells that GetString only argument is "The zero-based column ordinal". In your case you're trying to access second item (by index 1), but your query has only single field returned. Use index 0 in
answer = dataReader.GetString(0);

Simple SQL query with DataContext

I have a web-site connected to a SQL Server database, and I want to add a simple SQL query to it (for administrators). I was hoping to use the DataContext, and run a query, then return the results as a simple list. Is there any way to do this?
Using
string full_query = "SELECT " + query;
IEnumerable<string> results = DB.DB().ExecuteQuery<string>(full_query);
Doesn't work, throwing errors where ints come through. Changing the template parameter to "object" doesn't help much either.
So I need to run a select statement, and return the results as a list on a page.
Any ideas?
Normally you would want to use:
var results = DB.DB().SqlQuery(full_query);
If you want insert/update/delete, you can use:
DB.DB().ExecuteSqlCommand(full_query);
Hope it helps.
After a bit of messing around, I found something that works. I am using a class called DatabaseResults to hold the results:
public class DatabaseResults
{
public List<string> ColumnNames { get; set; }
public List<List<string>> Rows { get; set; }
public DatabaseResults()
{
ColumnNames = new List<string>();
Rows = new List<List<string>>();
}
}
The method then goes and runs the query, grabbing the headings and putting them in the results objects. It then reads the rows, taking the strings of the column values. "query" is the string passed in. It is the "select" query, with the select bit missing.
DatabaseResults results = new DatabaseResults();
string full_query = "SELECT " + query;
DbConnection connection = DB.DB().Connection;
connection.Open();
var command = connection.CreateCommand();
command.CommandText = full_query;
try
{
using (var reader = command.ExecuteReader())
{
for (int i = 0; i < reader.FieldCount; i++)
{
results.ColumnNames.Add(reader.GetName(i));
}
while (reader.Read())
{
List<string> this_res = new List<string>();
for (int i = 0; i < reader.FieldCount; ++i)
{
this_res.Add(reader[i].ToString());
}
results.Rows.Add(this_res);
}
}
}
catch (Exception ex)
{
results.ColumnNames.Add("Error");
List<string> this_error = new List<string>();
this_error.Add(ex.Message);
results.Rows.Add(this_error);
}
finally
{
connection.Close();
}
I can't destroy the connection, as it is used by the systems db object, so I need to open and close it. The try/catch/finally makes sure this happens.

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

Stored Procedure Parameter with AddWithValue()

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

Categories