Using SQL datareader to extract 1 to many custom object in c# - c#

I'm having issues with the SQL reader class.
I have a custom object named Airport on the database. But im having trouble using the datareader correctly. When i try to extract all Airports into a list(see method 2 below) it seems to jump of the while(_reader.Read()) loop before adding the object to the list.
Any suggestions?
To extract the object do i use 3 methods:
To find a specific object:
public Airport FindAirportByCode(string _airportCode)
{
con.Open();
string query = "SELECT * from Airport WHERE airportCode = '" + _airportCode + "'";
SqlCommand cmd = new SqlCommand(query, con);
SqlDataReader _reader = cmd.ExecuteReader();
Airport retAirport = BuildAirport(_reader);
_reader.Close();
con.Close();
return retAirport;
}
To get all Airports into a list
public List<Airport> SelectAll()
{
con.Open();
List<Airport> airports = new List<Airport>();
string query = "SELECT * from Airport";
SqlCommand cmd = new SqlCommand(query, con);
SqlDataReader _reader = cmd.ExecuteReader();
while (_reader.Read())
{
Airport temAirport = new Airport();
temAirport = BuildAirport(_reader);
airports.Add(temAirport); // It seems to skip this step and only add the last Airport from BuildAirport to the list.
}
_reader.Close();
con.Close();
return airports;
}
To build it into an object in C#
private Airport BuildAirport(SqlDataReader _reader)
{
Airport temAirport = new Airport();
while (_reader.Read())
{
temAirport.airportCode = (string) _reader["airportCode"];
temAirport.airportName = (string) _reader["airportName"];
temAirport.country = (string) _reader["country"];
temAirport.city = (string) _reader["city"];
}
_reader.Close();
return temAirport;
}

In third step you are enumarating reader again and that is why you get only one airport when you want to retrieve all of them.
private Airport BuildAirport(SqlDataReader _reader)
{
Airport temAirport = new Airport();
temAirport.airportCode = (string) _reader["airportCode"];
temAirport.airportName = (string) _reader["airportName"];
temAirport.country = (string) _reader["country"];
temAirport.city = (string) _reader["city"];
return temAirport;
}
And now we have to change your first method because changing BuildAirport breaks it. We have to read one row in FindAirportByCode now.
public Airport FindAirportByCode(string _airportCode)
{
con.Open();
string query = "SELECT * from Airport WHERE airportCode = #airportCode";
SqlCommand cmd = new SqlCommand(query, con);
cmd.Parameters.AddWithValue("#airportCode", _airportCode);
SqlDataReader _reader = cmd.ExecuteReader();
Airport retAirport = null;
if (_reader.Read())
{
retAirport = BuildAirport(_reader);
}
_reader.Close();
con.Close();
return retAirport;
}
Use parametrized queries for security.

Related

How to fix code to return full list instead of one row from SqlDataReader in C# Web API

I am building a web API that connects to SQL and am trying to return all rows from the db in which the "StartDateTime" value is equal to today's date. For some reason, I am only getting one row instead of all rows that fit the definition. What do I need to change?
public class EventsController : ApiController
{
public List<tblEventDate> Get()
{
using (CalendarEntities entities = new CalendarEntities())
{
tblEventDate singleEvent = new tblEventDate();
List<tblEventDate> eventList = new List<tblEventDate>();
string strcon = ConfigurationManager.ConnectionStrings["DbConnCalendar"].ConnectionString;
SqlConnection con = new SqlConnection(strcon);
con.Open();
string comm = "SELECT [dbo].[tblEventDates].[EventID], [dbo].[tblEventDates].[StartDateTime], WHERE (CONVERT(date, dbo.tblEventDates.StartDateTime) = CONVERT(date, GETDATE()))";
SqlCommand cmd = new SqlCommand(comm, con);
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.HasRows)
{
while (reader.Read())
{
singleEvent.StartDateTime = (DateTime)(reader["StartDateTime"]);
singleEvent.EventID = (long)reader["EventID"];
eventList.Add(singleEvent);
}
reader.NextResult();
}
con.Close();
}
return eventList;
}
}
}
Try this:
conn.Open();
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
if (reader.HasRows)
{
singleEvent.StartDateTime = (DateTime)(reader["StartDateTime"]);
singleEvent.EventID = (long)reader["EventID"];
eventList.Add(singleEvent);
}
}

SQL query result assertion

I have put together the following method:
public static ArrayList DbQueryToArry()
{
string SqlCString = "connString";
SqlConnection connection = null;
ArrayList valuesList = new ArrayList();
connection = new SqlConnection(SqlCString);
connection.Open();
SqlCommand command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader[0]));
}
return valuesList;
}
I'd like to be able to run an assertion like this one:
var a = DbQueryToArry();
Assert.IsTrue(a.Contains("some value"));
Given reader [0]
valuesList.Add(Convert.ToString(reader[0]));
I only get the first column (CLIENTINFO) into the array and not the second (ACCOUNT_Purpose). How should I modify the code to get both ?
In addition, the returned values could be either a String or Int so would my current code version should be handling both ?
Thanks in advance.
If we switch from obsolete ArrayList to something like IEnumerable<T>:
public static IEnumerable<IDataRecord> DbQueryToArray(string sql) {
if (null == sql)
throw new ArgumentNullException(nameof(sql));
//TODO: do not hardcode connetcion string but read it (say, from Settings)
string SqlCString = "connString";
//DONE: Wrap IDisposable into using
using (SqlConnection connection = new SqlConnection(SqlCString)) {
connection.Open();
//DONE: Wrap IDisposable into using
using (SqlCommand command = new SqlCommand(sql, connection)) {
//DONE: Wrap IDisposable into using
using (SqlDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
yield return reader as IDataRecord;
}
}
}
}
}
then you can use Linq in order to query the result:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT");
Assert.IsTrue(a.Any(record =>
Convert.ToString(record["CLIENTNO"]) == "some value"));
Assert.IsTrue(a.Any(record =>
Convert.ToString(record["ACCOUNT_Purpose"]) == "some other value"));
If you don't want execute query several times, you can materialize the results:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT")
.ToList();
Assert.IsTrue(a.Any(record => Convert.ToString(record[0]) == "some value"));
Assert.IsTrue(a.Any(record => Convert.ToString(record[1]) == "some other value"));
Finally (see comments below), if we want to test if any field in any record has the value:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT")
.SelectMany(line => {
// Flatten the cursor into IEnumerable<String>
string[] result = new string[line.FieldCount];
for (int i = 0; i < result.Length; ++i)
result[i] = Convert.ToString(line[i]);
return result;
});
a.Any(item => item == "some value");
It is because you only read the first value of the reader. Reader.Read() read each row one by one and Convert.ToString(reader[0])) means that you want to read the first column as string.
That's cause you are getting only the first column. You can do something like below by specifying the column name
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader["CLIENTNO"]));
valuesList.Add(Convert.ToString(reader["ACCOUNT_Purpose"]));
}
Moreover, since you are converting all the columns to string; I would suggest to use a strongly typed collection like List<string> rather then ArrayList valuesList = new ArrayList();
The other answers are good, However some concerns
Lets stop using ArrayList, and use a List<T> instead
Lets use a using statement where you can
Note : I have used a ValueTuple to return more than 1 field
Example
public static List<(string clientNo, string account)> DbQueryToArray()
{
const string SqlCString = "connString";
var valuesList = new List<(string clientNo, string account)>();
using (var connection = new SqlConnection(SqlCString))
{
connection.Open();
using (var command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection))
{
var reader = command.ExecuteReader();
while (reader.Read())
valuesList.Add(((string)reader[0],(string)reader[1]) );
}
}
return valuesList;
}
Usage
var results = DbQueryToArray();
Assert.IsTrue(results.Any(x => x.clientNo == someValue || x.account == someValue));
best practice is to check first if the reader has rows
reader.HasRows
then close the reader and the connection
your code should look like this:
public static ArrayList DbQueryToArry()
{
string SqlCString = "connString";
SqlConnection connection = null;
ArrayList valuesList = new ArrayList();
connection = new SqlConnection(SqlCString);
using (connection)
{
connection.Open();
SqlCommand command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection);
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader[0]));
valuesList.Add(Convert.ToString(reader[1])); // add to valuelist
}
}
reader.Close(); // close reader
} //dispose connection
return valuesList;
}
Use DataTable and SqlDataAdapter to get query result in form of table. Something like this:
string connString = #"your connection string here";
string query = "select * from table";
DataTable dataTable = new DataTable();
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(query, conn);
conn.Open();
// create data adapter
SqlDataAdapter da = new SqlDataAdapter(cmd);
// this will query your database and return the result to your datatable
da.Fill(dataTable);
conn.Close();
da.Dispose();
Then you can use dataTable object to see if particular values exist.

How to store multiple values in a struct array using SqlDataReader

I'd like to populate an User array with data from my SQL Server database using a SqlDataReader.
This is my code so far:
public struct User
{
public int id;
public string log;
public string password;
public User (int id1,string s, s2)
{
id=id1;
log =s;
password=s2;
}
}
User[] al = new User[50];
int i=0;
using (SqlConnection connection = new SqlConnection("string")
{
connection.Open();
SqlCommand command = new SqlCommand("Select [UserName], [Password]. from [TaUser]", connection);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// populate the al array with the datas from the 3 columns : ID, UserName, Password
}
}
connection.Close();
}
I know that if I had a simple arraylist I could just do al.Add(""), however, I struggle when it comes to struct arrays.
There are a lots of errors in your code.
First, your User constructor is invalid, it should be:
public User(int id1, string s, string s2)
Second, your query does not return user id.
Third, it would probably be better to use List instead of array.
With all that, this should work
List<User> userList = new List<User>() ;
using (SqlConnection connection = new SqlConnection("string")
{
connection.Open();
SqlCommand command = new SqlCommand("Select [Id], [UserName], [Password]. from [TaUser]", connection);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var userName = reader.GetString(1);
var pwd = reader.GetString(2);
var user = new User(id, userName, pwd);
userList.Add(user);
}
}
connection.Close();
// if you really need an array, do it here
var al = userList.ToArray()
I'd advise doing something like this:
SqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
Then read out of that DataTable like this:
string name = dataTable.Rows[0]["UserName"] as string;
Then fill your User struct with the gathered info. Job Done?

Using IEnumerable<IDataRecord> to return data

I am trying to return data using IEnumerable with given fields, where I am calling the the method I want to reference the data with given field name and return that.
Example, here is the function
public IEnumerable<IDataRecord> GetSomeData(string fields, string table, string where = null, int count = 0)
{
string sql = "SELECT #Fields FROM #Table WHERE #Where";
using (SqlConnection cn = new SqlConnection(db.getDBstring(Globals.booDebug)))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("#Fields", SqlDbType.NVarChar, 255).Value = where;
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return (IDataRecord)rdr;
}
}
}
}
Calling:
IEnumerable<IDataRecord> data = bw.GetSomeData("StaffCode, Perms", "BW_Staff", "StaffCode = 'KAA'");
What must I do to return the data this way or what way ?
string staffCode = data["StaffCode"].ToString();
string perms = data["Perms"].ToString();
Thanks for any help
your data variable is a collection of rows. You need to iterate over the collection to do something interesting with each row.
foreach (var row in data)
{
string staffCode = row["StaffCode"].ToString();
string perms = row["Perms"].ToString();
}
Update:
Based on your comment that you only expect GetSomeData(...) to return a single row, I'd suggest 1 of two things.
Change the signature of GetSomeData to return an IDataRecord. and remove "yield" from the implementation.
public IDataRecord GetSomeData(string fields, string table, string where = null, int count = 0)
{
string sql = "SELECT #Fields FROM #Table WHERE #Where";
using (SqlConnection cn = new SqlConnection(db.getDBstring(Globals.booDebug)))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("#Fields", SqlDbType.NVarChar, 255).Value = where;
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
return (IDataRecord)rdr;
}
}
}
}
}
Or
var row = data.FirstOrDefault();
if (row != null)
{
string staffCode = row["StaffCode"].ToString();
string perms = row["Perms"].ToString();
}
Remarks:
Your implementation of GetSomeData is incomplete. You are not even using several of the parameters, most importantly the fields parameter. And conceptually in SQL you can't parameterize which fields get returned or which table gets used (etc.), but rather you need to construct a dynamic query and execute it.
Update 2
Here is an implementation of GetSomeData that constructs a proper query (in C# 6, let me know if you need it in an earlier version).
public IEnumerable<IDataRecord> GetSomeData(IEnumerable<string> fields, string table, string where = null, int count = 0)
{
var predicate = string.IsNullOrWhiteSpace(where) ? "" : " WHERE " + where;
string sql = $"SELECT { string.Join(",", fields) } FROM {table} {predicate}";
using (SqlConnection cn = new SqlConnection(db.getDBstring(Globals.booDebug)))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return (IDataRecord)rdr;
}
}
}
}
And here is how you would use it.
IEnumerable<IDataRecord> data = bw.GetSomeData(new[] { "StaffCode", "Perms" }, "BW_Staff", "StaffCode = 'KAA'");
You can either enumerate it or call .FirstOrDefault, it's your choice. Each time you call GetSomeData, it will run the query.
Update 3
GetSomeData implemented with earlier versions of C#
public IEnumerable<IDataRecord> GetSomeData(IEnumerable<string> fields, string table, string where = null, int count = 0)
{
var predicate = string.IsNullOrEmpty(where) ? "" : " WHERE " + where;
string sql = string.Format("SELECT {0} FROM {1} {2}", string.Join(",", fields), table, predicate);
using (SqlConnection cn = new SqlConnection(db.getDBstring(Globals.booDebug)))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return (IDataRecord)rdr;
}
}
}
}

getting an error when trying to find value in database

So I have a textbox and a button. I want to enter a value into the textbox and it will find the value that i entered in the row from the table called 'ticket'. I also then want to create a 'rebate slip' object in a table called 'Rebateslip'. This table has 2 fields - an id which is autoincremented and the ticketId which is the value I entered in the textbox (so basically I'm just passing the ticketId I just found into the 'RebateSlip' table). I put all the functions in a class called 'model' then I call them in the button event handler.
public bool rebateslip(int ticketID)
{
SqlCommand myCommand = new SqlCommand("SELECT ID FROM ticket WHERE ID = #ticketID", con);
SqlDataAdapter sqlDa = new SqlDataAdapter(myCommand);
myCommand.Parameters.AddWithValue("#ticketID", ticketID);
var reader = myCommand.ExecuteReader();
return reader.HasRows;
}
public void createRebateslip(int ticketId)
{
SqlCommand myCommand = new SqlCommand("INSERT INTO RebateSlip (ticketId) VALUES (#ticketId); SELECT SCOPE_IDENTITY();", con);
myCommand.Parameters.AddWithValue("#ticketId", ticketId);
string insertID = (myCommand.ExecuteScalar()).ToString();
rebateSlipList.Add(new RebateSlip(Int32.Parse(insertID), ticketId));
}
It works for one number but if I try enter another number into the textbox I get the error there is already an open DataReader associated with command which must be closed first.
Here's the code for the button. If the value is found in the table then a 'rebatelip' should be created but that is also not working.
private void buttonPrintRebateSlip_Click(object sender, EventArgs e)
{
int id;
if (!int.TryParse(textBoxRebateSlip.Text, out id))
{
return;
}
if (model.rebateslip(id))
{
MessageBox.Show("Found ticket");
//create rebate slip object
model.createRebateslip(id);
textBoxRebateSlip.Clear();
}
else
{
MessageBox.Show("Ticket not in database");
textBoxRebateSlip.Clear();
}
}
Excerpt from SqlDataReader.Close Method:
You must explicitly call the Close method when you are through using
the SqlDataReader to use the associated SqlConnection for any other
purpose.
You should use the using Statement.
public bool rebateslip(int ticketID)
{
using(SqlCommand myCommand = new SqlCommand("SELECT ID FROM ticket WHERE ID = #ticketID", con))
{
using(SqlDataAdapter sqlDa = new SqlDataAdapter(myCommand))
{
myCommand.Parameters.AddWithValue("#ticketID", ticketID);
using(var reader = myCommand.ExecuteReader())
{
return reader.HasRows;
}
}
}
}
Anyway, your sqlDa SqlDataAdapter makes no sense in the context. Probably you only need:
public bool rebateslip(int ticketID)
{
using(SqlCommand myCommand = new SqlCommand("SELECT ID FROM ticket WHERE ID = #ticketID", con))
{
myCommand.Parameters.AddWithValue("#ticketID", ticketID);
using(var reader = myCommand.ExecuteReader())
{
return reader.HasRows;
}
}
}
reader.close once you're done with it.
Also -- you should use a using or try...catch block
This way you never have a reader variable. Still the using paradigm is better.
public bool rebateslip(int ticketID)
{
SqlCommand myCommand = new SqlCommand("SELECT ID FROM ticket WHERE ID = #ticketID", con);
SqlDataAdapter sqlDa = new SqlDataAdapter(myCommand);
myCommand.Parameters.AddWithValue("#ticketID", ticketID);
return myCommand.ExecuteReader().HasRows;
}
public void createRebateslip(int ticketId)
{
SqlCommand myCommand = new SqlCommand("INSERT INTO RebateSlip (ticketId) VALUES (#ticketId); SELECT SCOPE_IDENTITY();", con);
myCommand.Parameters.AddWithValue("#ticketId", ticketId);
int insertID = (int)myCommand.ExecuteScalar();
rebateSlipList.Add(new RebateSlip(insertID, ticketId));
}
Also it seems you are storing ticketID's in the Insert table (from insertID) and insertID's in the Ticket table (from ticketID). Instead you should use a third table where you store only insertID's and ticketID's, allowing a multiple to multiple relationships while allowing them to be changed/deleted with a single action.

Categories