Testing DB content, Zooming in on 1 value - c#

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

Related

Compare text file data with database records in C#

I have to make a select statement on a database and than compare the results with a text file using only C# in Visual Studio. If the text file has a bigger value than the record in the database, the program returns the value from the text file, and if the database record has a bigger value, the program returns the value from the database. The results are added to a list being a class WynikPorownania{}. The user types in the file an index of a product and the value(in this case the value is it's availability condition).
For example:
The textfile says this:
WYR_WR_CZ1=12
VIDIS_JIMU_BOX=3
REREK_KOTEK_T=5
In the database, theses indexes are connected to an availability condition like this
WYR_WR_CZ1=-1.0000
VIDIS_JIMU_BOX=-13.0000
REREK_KOTEK_T=0.0000
Now the program should return the bigger value in a list being the WynikPorownania{} class.
For now, I managed to do this much: I took every record from the select query and put it in to a list as a class. I have a function that checks the "standysp"(availability condition) for a specified index, and I asigned the text file value to a string. I think that it could be done maybe with this "zwr" function and maybe a loop but I don't really know where to go from now on. This is my code for now:
using System;
using FirebirdSql.Data.FirebirdClient;
using System.Collections.Generic;
using System.Linq;
using System.IO;
namespace dokselect
{
class indexstan
{
public string index;
public double standysp;
}
class WynikPorownania
{
public string Indeks;
public int Ilosc;
}
class Program
{
public static void Main()
{
///////CONNECTION
string conn = "database=C:/PCBiznes/BAZA/IXION2_LOGMAG.FB;user=SYSDBA;password=masterkey;DataSource=192.168.24.112;Port=3050";
FbConnection myConnection = new FbConnection(conn);
FbDataReader myReader = null;
string sql = "select KARTOTEKA.indeks,STANMAG.standysp FROM kartoteka JOIN stanmag using(ID_KARTOTEKA);";
FbCommand myCommand = new FbCommand(sql, myConnection);
myConnection.Open();
myReader = myCommand.ExecuteReader();
///////LIST lista1
List<indexstan> lista1 = new List<indexstan>();
double standysp;
string index;
while (myReader.Read())
{
index = myReader[0].ToString();
standysp = Convert.ToDouble(myReader[1]);
lista1.Add(new indexstan { index=index, standysp=standysp });
//Console.WriteLine(myReader[0].ToString());
}
myConnection.Close();
Console.WriteLine(lista1.Count);
//RETURN STANDYSP FUNCTION
double zwr(string myIndex)
{
var result = lista1.FirstOrDefault(lista1 => lista1.index == myIndex).standysp;
return result;
}
zwr("EMPIS_DESKA_FASOLKA");
//READ FROM TXT
string path = "C:/Users/Praktykant/Documents/textdocs/dok1.txt";
string plik = File.ReadAllText(path);
//Console.WriteLine(plik);
StreamReader objReader = new StreamReader(path);
myConnection.Close();
}
}
You mean something like this?
var list = File.ReadAllLines(path).Select(line =>
{
var tokens = line.Split("=");
var index = tokens[0];
var value = int.Parse(tokens[1]);
return new WynikPorownania
{
Indeks = index,
Ilosc = (int)Math.Max(value, zwr(index).standysp)
}
}).ToList();
(Depending on the size of the file, a StreamReader would be better than ReadAllLines)
You're almost there.
Extract text file line by line, and compute bigger value
StringReader r = new StringReader(plik);
string line;
while((line = r.ReadLine()) != null)
{
string index = line.Split('=')[0];
string textValue = line.Split('=')[1];
double biggerValue = Math.Max(
zwr(index),
double.Parse(textValue);
Console.WriteLine($"{index} > {biggerValue}");
}

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

How to return names of all columns?

Searching, I found the PRAGMA as a possible solution for my problem, but it only returns the index of each column. There's any other method to return all columns names?
I thought using a For to go through my column indexes returning their names would works fine, but I dont exactly know how the syntax of this would be, either the stop condition.
void FillColumnList()
{
try
{
string check = "SELECT * FROM PRAGMA table_info(Produtos)";
sqlCon.Open();
SQLiteCommand tst2 = new SQLiteCommand(check, sqlCon);
SQLiteDataReader rdr2 = tst2.ExecuteReader();
if (rdr2.HasRows)
{
while (rdr2.Read())
{
string columns = rdr2[0].ToString();
Columns.Add(columns);
}
sqlCon.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
This code should return and fill the Global variable list Columns with the name of each column of "Produtos" table. Instead of it, my DataReader 'rdr2' return false in the HasRows, even when there's columns and Datas in my table "Produtos"
You can use the connection's GetSchema method to retrieve the column information. I'm using the following code to insert information my own class TableColumn not shown here:
string[] restrictions = new string[] { null, null, tableName };
using (DataTable columns = conn.GetSchema("Columns", restrictions)) {
int nameIndex = columns.Columns.IndexOf("COLUMN_NAME");
int ordinalPosIndex = columns.Columns.IndexOf("ORDINAL_POSITION");
int isNullableIndex = columns.Columns.IndexOf("IS_NULLABLE");
int maxLengthIndex = columns.Columns.IndexOf("CHARACTER_MAXIMUM_LENGTH");
int dataTypeIndex = columns.Columns.IndexOf("DATA_TYPE");
int isPrimaryKeyIndex = columns.Columns.IndexOf("PRIMARY_KEY");
int hasDefaultIndex = columns.Columns.IndexOf("COLUMN_HASDEFAULT");
int defaultValueIndex = columns.Columns.IndexOf("COLUMN_DEFAULT");
foreach (DataRow row in columns.Rows) {
var col = new TableColumn {
ColumnName = (string)row[nameIndex]
};
try {
col.ColumnNameForMapping = prepareColumnNameForMapping(col.ColumnName);
} catch (Exception ex) {
throw new UnimatrixExecutionException("Error in delegate 'prepareColumnNameForMapping'", ex);
}
col.ColumnOrdinalPosition = (int)row[ordinalPosIndex];
col.ColumnAllowsDBNull = (bool)row[isNullableIndex];
col.ColumnMaxLength = (int)row[maxLengthIndex];
string explicitDataType = ((string)row[dataTypeIndex]).ToLowerInvariant();
col.ColumnDbType = GetColumnDbType(explicitDataType);
col.ColumnIsPrimaryKey = (bool)row[isPrimaryKeyIndex];
col.ColumnIsIdentity = explicitDataType == "integer" && col.ColumnIsPrimaryKey;
col.ColumnIsReadOnly = col.ColumnIsIdentity;
if ((bool)row[hasDefaultIndex]) {
col.ColumnDefaultValue = GetDefaultValue(col.ColumnDbType, (string)row[defaultValueIndex]);
if (col.ColumnDefaultValue == null) { // Default value could not be determined. Probably expression.
col.AutoAction = ColumnAction.RetrieveAfterInsert;
}
}
tableSchema.ColumnSchema.Add(col);
}
}
You can simplify this code considerably if you only need the column names.

SQL query get error "Specified cast is not valid."

I have sample codes as below:-
public List<Announcement_User> announcementUser([FromBody]MyAnnouncementUser value)
{
MySqlConnection conn = WebApiConfig.conn();
MySqlCommand query = conn.CreateCommand();
query.CommandText = "select a.title,a.description,a.date_created,ua.read,ua.announcement_id,ua.user_announcement_id from announcement a left join user_announcement ua on a.announcement_id = ua.announcement_id where ua.user_id = #user_id";
query.Parameters.AddWithValue("#user_id", value.user_id);
var prodWishlist = new List<Announcement_User>();
try
{
conn.Open();
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
prodWishlist.Add(new Announcement_User(null, null,null, false, 0, 0, ex.ToString()));
}
MySqlDataReader fetch_query = query.ExecuteReader();
while (fetch_query.Read())
{
prodWishlist.Add(new Announcement_User(fetch_query["title"].ToString(), fetch_query["description"].ToString(), fetch_query["date_created"].ToString(), (bool)fetch_query["read"], fetch_query.GetInt32(4), fetch_query.GetInt32(5), null));
}
conn.Close();
return prodWishlist;
}
And I hit error as below:-
"Message": "An error has occurred.",
"ExceptionMessage": "Specified cast is not valid.",
"ExceptionType": "System.InvalidCastException",
Now I suspect the error caused by bool. May I know how can I write correct way for bool in(fetch_query.Read())? Please help. Thank you.
Try using the GetBoolean method:
fetch_query.GetBoolean("read")
I would recommend to you to retrieve all of the values beforehand using column name instead of column index and create new object when required parameters have values:
string title = fetch_query["title"].ToString();
string description = fetch_query["description"].ToString();
// ...
object read = fetch_query["read"];
object integer1 = fetch_query[4];
// ...
// newest C# approach
if ( read != null && bool.TryParse(read.ToString(), out bool b_read) )
// old c# approach
bool b_read = false;
if ( read != null && bool.TryParse(read.ToString(), out b_read) )
Check for every required property
prodWishlist.Add(
new Announcement_User(
title,
description,
// ..
b_read,
i_integer1
//...
));

Is there an easy way to check multiple columns to see if the value is null?

I'm getting a parameter and getting a specific product from a stored procedure. This stored procedure is returning many columns (I think around 15-20). Unfortunately, some of these values may be null and this of course produces an error when I try to put them in a variable. Is there a simple way to check and see if these values are NULL so that it doesn't throw an exception or do I need to put an if statement before each assignment? If it helps, here is my code:
public List<Product> ReadSpecificProductDetails(string productAndURLID)
{
ConfigureDataAccess();
myCommand = new SqlCommand("[dbo].[GetSpecificProductDetails]", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
//myCommand.Parameters.Add(productAndURLID);
//myCommand.Parameters.Add(new SqlParameter("#ProductID", SqlDbType.SmallInt));
myCommand.Parameters.AddWithValue("#ProductID", Convert.ToInt16(productAndURLID));
try
{
try
{
myConnection.Open();
myReader = myCommand.ExecuteReader();
}
catch (SqlException exception)
{
exception.Data.Add("cstring", myConfiguration);
throw;
}
catch (InvalidOperationException ex)
{
ex.Data.Add("cStatus", myConnection.State);
throw;
}
//Create a 'Product' list to store the records in
while (myReader.Read())
{
int productID = Convert.ToInt16(myReader["ProductID"]);
string productName = (string)myReader["Name"];
string productNumber = (string)myReader["ProductNumber"];
//int quantitySum = Convert.ToInt16(myReader["QuantitySum"]);
string productColor = (string)myReader["Color"];
int productSafetyStockLevel = Convert.ToInt16(myReader["SafetyStockLevel"]);
int productReorderPoint = Convert.ToInt16(myReader["ReorderPoint"]);
decimal productStandardCost = Convert.ToDecimal(myReader["StandardCost"]);
decimal productListPrice = Convert.ToDecimal(myReader["ListPrice"]);
string productSize = (string)myReader["Size"];
decimal productWeight = Convert.ToDecimal(myReader["Weight"]);
int productDaysToManufacture = Convert.ToInt16(myReader["DaysToManufacture"]);
string productCategoryName = (string)myReader["PC.Name"];
string productSubCategoryName = (string)myReader["PS.Name"];
string productModelName = (string)myReader["PM.Name"];
DateTime productSellStartDate = Convert.ToDateTime(myReader["Sell_Start_Date"]);
DateTime productSellEndDate = Convert.ToDateTime(myReader["Sell_End_Date"]);
DateTime productDiscontinuedDate = Convert.ToDateTime(myReader["Discontinued_Date"]);
Product p = new Product(productID, productName, productNumber, productColor, productSafetyStockLevel, productReorderPoint, productStandardCost,
productListPrice, productSize, productWeight, productDaysToManufacture, productCategoryName, productSubCategoryName, productModelName, productSellStartDate,
productSellEndDate, productDiscontinuedDate);
myProducts.Add(p);
}
}
finally
{
myReader.Close();
myConnection.Close();
}
return myProducts;
}
int? x = dt.Field<int?>( "Field" );
Or
int y = dt.Field<int?>( "Field" ) ?? 0;
C# DBNull and nullable Types - cleanest form of conversion
The top is probably your best approach. You currently have some non nullable types you are populating. So if you want to be able to represent them as null use the top approach. Otherwise you can use the other option to give a default value.
LINQ will be your best friend in these types of situations, as ssg suggested in the comments, however if you want to make a very large addition of null checking, you could implement something like the following:
int productID = (myReader["ProductID"] != null) ? Convert.ToInt16(myReader["ProductID"]) : 0;
or you could simply use an if statement.
Peform a check for DBNULL for the field you may suspect is a null value. In this case all of them.
if (! DBNull.Value.Equals(row[fieldName]))
return (string) row[fieldName] + " ";
else
return String.Empty;
What you can do is create a function that has several overloads for int, string, and DateTime parameters that checks for null
So it would look something like this:
public string CheckNull(string value)
{
if (! DBNull.Value.Equals(value))
return value;
else
return String.Empty;
}
This way you would only have to do something like
object.stringProperty = (string)CheckNull(row["columnName"]);
When using the direct mapping between a data reader and DTO, I use the IsDbNull() method of the data reader. Here are a couple of examples using different data types:
myObj.MyStringProperty = !myReader.IsDBNull("myStringColumn") ? myReader["myStringColumn"].ToString() : null;
myObj.MyNullableDecimalProperty = !myReader.IsDBNull("mydecimalColumn") ? decimal.Parse(myReader["mydecimalColumn"].ToString()) : new decimal?();

Categories