Simple SQL query with DataContext - c#

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.

Related

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

SQL ERROR System.Collections.Generic.List`1[Dapper.SqlMapper+DapperRow]

I would like my code to return a value but it returns me:
System.Collections.Generic.List`1[Dapper.SqlMapper+DapperRow]
I can't solve and make a right output process.
public static string Test()
{
using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
{
var vartest = cnn.Query("select grado from utenti where id='10'");
//var result = output.ToDictionary(row => (string)row.Grado, row => (string)row.Nome ) ; (commento)
//Console.WriteLine(vartest);
cnn.Close();
return vartest.ToString();
}
}
Query and Query<T> return multiple rows; Query is for dynamic rows; Query<T> is for typed rows. There are QueryFirst[<T>] and QuerySingle[<T>] for single rows.
If you're after a single value of a known type, then perhaps:
var vartest = cnn.QuerySingle<string>("select grado from utenti where id='10'");
if you mean your return vartest.ToString(); is returning you the string "System.Collections.Generic.List`1[Dapper.SqlMapper+DapperRow]" it is because your vartest is a list of items, you need to .ToString the item in the list
public static string ManagerFindid()
{
using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
{
var select = cnn.Query("select id from utenti");
if (select.Any())
{
return select[0].ToString();
// or do something with all the items in your list
foreach(string value in select)
{
//add value into list view
}
}
else
{
//this is hit when there are no items returned from the select query
return "Nothing Returned from Query";
}
}
you could also handle there being multiple items returned from your SQL
foreach(string value in select)
{
//do something with current value
}

IBM db2Command SQL SELECT * FROM IN #namedParameter

When using the C# code below to construct a DB2 SQL query the result set only has one row. If I manually construct the "IN" predicate inside the cmdTxt string using string.Join(",", ids) then all of the expected rows are returned. How can I return all of the expected rows using the db2Parameter object instead of building the query as a long string to be sent to the server?
public object[] GetResults(int[] ids)
{
var cmdTxt = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #ids ) ";
var db2Command = _DB2Connection.CreateCommand();
db2Command.CommandText = cmdTxt;
var db2Parameter = db2Command.CreateParameter();
db2Parameter.ArrayLength = ids.Length;
db2Parameter.DB2Type = DB2Type.DynArray;
db2Parameter.ParameterName = "#ids";
db2Parameter.Value = ids;
db2Command.Parameters.Add(db2Parameter);
var results = ExecuteQuery(db2Command);
return results.ToArray();
}
private object[] ExecuteQuery(DB2Command db2Command)
{
_DB2Connection.Open();
var resultList = new ArrayList();
var results = db2Command.ExecuteReader();
while (results.Read())
{
var values = new object[results.FieldCount];
results.GetValues(values);
resultList.Add(values);
}
results.Close();
_DB2Connection.Close();
return resultList.ToArray();
}
You cannot send in an array as a parameter. You would have to do something to build out a list of parameters, one for each of your values.
e.g.: SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( #id1, #id2, ... #idN )
And then add the values to your parameter collection:
cmd.Parameters.Add("#id1", DB2Type.Integer).Value = your_val;
Additionally, there are a few things I would do to improve your code:
Use using statements around your DB2 objects. This will automatically dispose of the objects correctly when they go out of scope. If you don't do this, eventually you will run into errors. This should be done on DB2Connection, DB2Command, DB2Transaction, and DB2Reader objects especially.
I would recommend that you wrap queries in a transaction object, even for selects. With DB2 (and my experience is with z/OS mainframe, here... it might be different for AS/400), it writes one "accounting" record (basically the work that DB2 did) for each transaction. If you don't have an explicit transaction, DB2 will create one for you, and automatically commit after every statement, which adds up to a lot of backend records that could be combined.
My personal opinion would also be to create a .NET class to hold the data that you are getting back from the database. That would make it easier to work with using IntelliSense, among other things (because you would be able to auto-complete the property name, and .NET would know the type of the object). Right now, with the array of objects, if your column order or data type changes, it may be difficult to find/debug those usages throughout your code.
I've included a version of your code that I re-wrote that has some of these changes in it:
public List<ReturnClass> GetResults(int[] ids)
{
using (var conn = new DB2Connection())
{
conn.Open();
using (var trans = conn.BeginTransaction(IsolationLevel.ReadCommitted))
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
var parms = new List<string>();
var idCount = 0;
foreach (var id in ids)
{
var parm = "#id" + idCount++;
parms.Add(parm);
cmd.Parameters.Add(parm, DB2Type.Integer).Value = id;
}
cmd.CommandText = "SELECT DISTINCT ID,COL2,COL3 FROM TABLE WHERE ID IN ( " + string.Join(",", parms) + " ) ";
var resultList = new List<ReturnClass>();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var values = new ReturnClass();
values.Id = (int)reader["ID"];
values.Col1 = reader["COL1"].ToString();
values.Col2 = reader["COL2"].ToString();
resultList.Add(values);
}
}
return resultList;
}
}
}
public class ReturnClass
{
public int Id;
public string Col1;
public string Col2;
}
Try changing from:
db2Parameter.DB2Type = DB2Type.DynArray;
to:
db2Parameter.DB2Type = DB2Type.Integer;
This is based on the example given here

No value given for required parameters, with value

I'm working in a c#/ASP.NET on a web app.
As part of the app, I want to connect to an ms-access database, and get values from it into a dataset.
For some reason, I get the error in the title when filling the dataset with a DataAdaptor- despite this, when I use breakpoints, I can see the command is as follows:
SELECT ..... WHERE ItemID = #value0 (if you need the parameters, ask for them and i`ll copy the whole thing).
I can also see that #value0 has the value 2 with breakpoints, and I am assured it's the only value in the query.
My question is, how could this happen? If the only value in the query is filled, what am I missing?
EDIT:
Full query:
SELECT ItemName as Name,ItemPicture as Picture,ItemHeroModif as Assistance,ItemTroopModif as Charisma, HerbCost as Herbs, GemCost as Gems FROM Item WHERE ItemID = #value0"
Full building code (Generating the query for each user requires a different amount of items, this one has only a single item, so i've used it to test):
static public DataSet getUserShopItemDS(string username,List<shopItem> items)
{
string madeForCommand = "SELECT ItemName as Name,ItemPicture as Picture,ItemHeroModif as Assistance,ItemTroopModif as Charisma, HerbCost as Herbs, GemCost as Gems FROM Item WHERE ItemID = ";
int count = 0;
foreach (shopItem item in items)
{
madeForCommand += "#value"+count+" OR ";
count++;
}
madeForCommand = madeForCommand.Substring(0, madeForCommand.Length - 3);
OleDbCommand command = GenerateConnection(madeForCommand);
for (int ii = 0; ii < items.Count; ii++)
{
command.Parameters.AddWithValue("#value" + ii, items[ii].ID);
}
var FreestyleAdaptor = new OleDbDataAdapter();
FreestyleAdaptor.SelectCommand = command;
DataSet Items = new DataSet();
FreestyleAdaptor.Fill(Items);//The error is thrown here.
return Items;
}
EDIT 2: - The shopitem class:
public class shopItem
{
//Irrelevant parameters,
public int ID // Whenever a user logs in, I create a list of his items, and attach each one it's ID from the database. I send the list as a parameter to the function.
{
get;
private set;
}
public shopItem(int ID)
{
this.ID = ID;
}
public shopItem() { }
// more constructors.
}
Here's the code you should use:
var madeForCommand = "SELECT ItemName as Name,ItemPicture as Picture,ItemHeroModif as Assistance,ItemTroopModif as Charisma, HerbCost as Herbs, GemCost as Gems FROM Item WHERE ";
OleDbCommand command = new OleDbCommand();
for (int ii = 0; ii < items.Count; ii++)
{
madeForCommand += "ItemId = ? OR ";
command.Parameters.AddWithValue("value" + ii, items[ii].ID);
}
madeForCommand = madeForCommand.Substring(0, madeForCommand.Length - 3);
In MS Access parameters are passed using ? and have to be provided in the same order as they are used in the query.

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