Multiples Table in DataReader - c#

I normally use DataSet because It is very flexible. Recently I am assigned code optimization task , To reduce hits to the database I am changing two queries in a procedure. one Query returns the count and the other returns the actual data. That is , My stored procedure returns two tables. Now, I know how to read both tables using DataSets, But I need to read both tables using DataReader. In search of that I found This.
I follow the article and wrote my code like this:
dr = cmd.ExecuteReader();
while (dr.Read())
{
}
if (dr.NextResult()) // this line throws exception
{
while (dr.Read())
{
But I am getting an exception at dt.NextResult. Exception is :
Invalid attempt to call NextResult when reader is closed.
I also googled above error , but still not able to solve the issue.
Any help will be much appreciated. I need to read multiple tables using datareader, is this possible?

Try this because this will close connection ,data reader and command once task get over , so that this will not give datareader close exception
Also do check like this if(reader.NextResult()) to check there is next result,
using (SqlConnection connection = new SqlConnection("connection string here"))
{
using (SqlCommand command = new SqlCommand
("SELECT Column1 FROM Table1; SELECT Column2 FROM Table2", connection))
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
MessageBox.Show(reader.GetString(0), "Table1.Column1");
}
if(reader.NextResult())
{
while (reader.Read())
{
MessageBox.Show(reader.GetString(0), "Table2.Column2");
}
}
}
}
}

I have tried to reproduce this issue (also because i haven't used multiple tables in a reader before). But it works as expected, hence i assume that you've omitted the related code.
Here's my test code:
using (var con = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
using (var cmd = new SqlCommand("SELECT TOP 10 * FROM tabData; SELECT TOP 10 * FROM tabDataDetail;", con))
{
int rowCount = 0;
con.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
String object1 = String.Format("Object 1 in Row {0}: '{1}'", ++rowCount, rdr[0]);
}
if (rdr.NextResult())
{
rowCount = 0;
while (rdr.Read())
{
String object1 = String.Format("Object 1 in Row {0}: '{1}'", ++rowCount, rdr[0]);
}
}
}
}
}

I built on Pranay Rana's answer because I like keeping it as small as possible.
string rslt = "";
using (SqlDataReader dr = cmd.ExecuteReader())
{
do
{
while (dr.Read())
{
rslt += $"ReqID: {dr["REQ_NR"]}, Shpr: {dr["SHPR_NR"]}, MultiLoc: {dr["MULTI_LOC"]}\r\n";
}
} while (dr.NextResult());
}

The question is old but I find the answers are not correct.
Here's how I do it:
List<DataTable> dataTables = new();
using IDataReader dataReader = command.ExecuteReader();
do
{
DataTable dataTable = new();
dataTable.Load(dataReader);
dataTables.Add(dataTable);
}
while (!dataReader.IsClosed);

Related

Access Query on C# Not Generating Results

I am using the following Access query on C# MVC to compare two tables and return records that fall within the date range and machine selected by the user. The query code runs perfectly on the actual Access database but I guess something is wrong with the connection string and the code to return the results. I am not sure what's wrong with the code and I would appreciate if someone could help me determine what's wrong. Thanks!
C# MVC Controller code:
public ActionResult MissingChecksheets(string startDate, string endDate, string machine)
{
var query = $#"SELECT * FROM [TrackingLog]
WHERE [TrackingLog].[Workcenter] = '{machine}' AND
[TrackingLog].[Complete Date] > #{startDate}# AND
[TrackingLog].[Complete Date] < #{endDate}# AND
[TrackingLog].[Order Item] NOT IN (SELECT [OrderNum] FROM [dbo_Checksheet])";
var sheets = new List<Checksheet>();
using (var con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Checksheets.accdb;"))
{
using (var command = new OleDbCommand(query, con))
{
con.Open();
using (var reader = command.ExecuteReader())
{
while (reader.NextResult())
{
sheets.Add(new FabChecksheet
{
OrderNum = reader.GetString(0),
PartNum = reader.GetString(1)
});
}
}
}
}
return PartialView(sheets);
}
OleDbDataReader.NextResult() method used to move between result set if the query string has multiple result sets (e.g. more than two SELECT statements, not counting SELECT inside aggregate functions). Since your query has single result set, OleDbDataReader.Read() must be used to move between records:
using (var con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Checksheets.accdb;"))
{
using (var command = new OleDbCommand(query, con))
{
con.Open();
using (var reader = command.ExecuteReader())
{
// Only single result set, use 'Read' here
while (reader.Read())
{
sheets.Add(new FabChecksheet
{
OrderNum = reader.GetString(0),
PartNum = reader.GetString(1)
});
}
}
}
}
Note that if NextResult() is used, the data reader will move to next result set which has empty data.
Related issue:
Difference between SqlDataReader.Read and SqlDataReader.NextResult

SQL Server Column to Combobox?

I have a table called Product. One of the columns of this table is called Naam. It's the name of the product. When you press on the button, all product names have to be added to the combobox.
So if I have 2 products: Cola & Fanta.
The program has to show only the column Naam in the combobox. Not the other columns.
I have already this for my button, but it doesn't work.
db.AlleProducten("Select Naam from Product;", Product);
cb_product.Items.Add(Product.Naam);
And this is the method that runs the query:
public void AlleProducten(string commandText, product Product)
{
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(commandText, conn))
{
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
if (rdr.HasRows)
{
rdr.Read();
Product.Naam = rdr.GetString(1);
conn.Close();
}
}
}
}
The error:
An unhandled exception of type 'System.IndexOutOfRangeException' occurred in System.Data.dll
Additional information: De index ligt buiten de matrixgrenzen.
The additional information is in Dutch. Translated to English:
The index is located outside of the array bounds.
The first problem in your code is when you try to retrieve the value at index 1 of your SqlDataReader. Your query has only one column and in NET arrays start at index zero, so you need to retrieve the Naam value using this line
Product.Naam = rdr.GetString(0);
However, if you have more than one record to extract the Naam value then you need to loop using the SqlDataReader until it return false from the Read method and store the Naam values retrieve in some kind of collection structure.
I suggest to use a List<string>
public List<string> AlleProducten(string commandText)
{
List<string> names = new List<string>();
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(commandText, conn))
{
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
while(rdr.Read())
names.Add(rdr.GetString(0));
}
conn.Close();
}
return names;
}
The code above loops over the returned results of the SqlDataReader and add every Naam to a List of strings and finally returns the list to the caller.
In this way you can assign the return value of the AlleProducten method to the DataSource of the combobox
List<string>result = db.AlleProducten("Select Naam from Product;");
cb_product.DataSource = result;
1 - You are out of range due you are using rdr.GetString(1) instead of rdr.GetString(0)
2 - There isn't any ComboBox in your code.
using (var rdr = cmd.ExecuteReader())
{
while (reader.Read())
{
Product.Naam = rdr.GetString(0);
YourComboBox.Items.Add(Product.Naam);
}
}
Take a look at SqlCommand.ExecuteReader documentation.

Invalid attempt to call HasRows when reader is closed

I have this problem: Invalid attempt to call HasRows when reader is closed.
I have tried alot; removing connection close line, closing the connection in the end. but having same issue. I can't get whats wrong with my code.
try
{
con = new SqlConnection(ConfigurationManager.ConnectionStrings["TextItConnectionString"].ConnectionString);
using (con)
{
con.Open();
Library.writeErrorLog("connection build and open");
SqlCommand cmd = con.CreateCommand();
using (cmd)
{
cmd.CommandText = "Select [name] From [dbo].[Users]";
SqlDataReader reader = cmd.ExecuteReader();
using (reader)
{
user.dt.Load(reader);
if (reader.HasRows)
{
while (reader.Read())
{
Library.writeErrorLog(reader.GetString(0));
}
}
else
Library.writeErrorLog("no rows");
reader.Close();
con.Close();
}
}
}
//SqlDataAdapter adap = new SqlDataAdapter("Select [name] From [dbo].[Users]", con);
//adap.Fill(user.dt);
}
catch (Exception ex)
{
Library.writeErrorLog(ex);
}
Thanks for the help!
I assume that user.dt returns a DataTable. You know that DataTable.Load(reader) will consume all records of the resultset and advances the reader to the next set? I'm asking because you are using HasRows after you've already used DataTable.Load.
As Steve has commented
Looking at the reference source of DataTable.Load you could clearly
see that the DataReader is closed before exiting from the method.
So if there is no other resultset(f.e. SELECT * FROM T1;SELECT* from T2) the reader will be closed at the end of Load which will cause the exception if you try to use SqlDataReader.HasRows.
I'd call this a lack of documentation since it's mentioned nowhere on MSDN.
So either use
reader.Read and reader.GetString in a loop and add it to the DataTable manually,
use DataTable.Load and loop the table afterwards or
use SqlDataAdapter.Fill(table):
1) while loop and manually filling the table
using (SqlDataReader reader = cmd.ExecuteReader())
{
if(reader.HasRows)
{
while (reader.Read())
{
string name = reader.GetString(0);
user.dt.Rows.Add(name);
Library.writeErrorLog(name);
}
}
else
Library.writeErrorLog("no rows");
}
2) requires two loops, one in DataTable.Load and the foreach
using (SqlDataReader reader = cmd.ExecuteReader())
{
if(reader.HasRows)
{
user.dt.Load(reader); // all records added
foreach(DataRow row in user.dt.Rows)
{
string name = row.Field<string>(0);
Library.writeErrorLog(name);
}
}
else
Library.writeErrorLog("no rows");
}
3) another option is to use a SqlDataAdapter and it's Fill(dataTable) method:
using (var da = new SqlDataAdapter(cmd))
{
da.Fill(user.dt);
if (user.dt.Rows.Count > 0)
{
foreach (DataRow row in user.dt.Rows)
{
string name = row.Field<string>(0);
Library.writeErrorLog(name);
}
}
else
Library.writeErrorLog("no rows");
}
Side-note: you don't need to use reader.Close or con.Close if you use the using-statement.

SqlDataReader and database access concurrency

The easiest way to illustrate my question is with this C# code:
using (SqlCommand cmd = new SqlCommand("SELECT * FROM [tbl]", connectionString))
{
using (SqlDataReader rdr = cmd.ExecuteReader())
{
//Somewhere at this point a concurrent thread,
//or another process changes the [tbl] table data
//Begin reading
while (rdr.Read())
{
//Process the data
}
}
}
So what would happen with the data in rdr in such situation?
I actually tested this. Test code:
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString))
{
conn.Open();
using (SqlCommand comm = new SqlCommand("select * from test", conn))
{
using (var reader = comm.ExecuteReader())
{
int i = 0;
while (reader.Read())
{
if ((string)reader[1] == "stop")
{
throw new Exception("Stop was found");
}
}
}
}
}
To test, I initialized the table with some dummy data (making sure that no row with the value 'stop' was included). Then I put a break point on the line int i = 0;. While the execution was halted on the break point, I inserted a line in the table with the 'stop' value.
The result was that depending on the amount of initial rows in the table, the Exception was thrown/not thrown. I did not try to pin down where exactly the row limit was. For ten rows, the Exception was not thrown, meaning the reader did not notice the row added from another process. With ten thousand rows, the exception was thrown.
So the answer is: It depends. Without wrapping the command/reader inside a Transaction, you cannot rely on either behavior.
Obligatory disclaimer: This is how it worked in my environment...
EDIT:
I tested using a local Sql server on my dev machine. It reports itself as:
Microsoft SQL Server 2008 R2 (SP1) - 10.50.2550.0 (X64)
Regarding transactions:
Here's code where I use a transaction:
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["test"].ConnectionString))
{
conn.Open();
using (var trans = conn.BeginTransaction())
using (SqlCommand comm = new SqlCommand("select * from test", conn, trans))
{
using (var reader = comm.ExecuteReader())
{
int i = 0;
while (reader.Read())
{
i++;
if ((string)reader[1] == "stop")
{
throw new Exception("Stop was found");
}
}
}
trans.Commit();
}
}
In this code, I create the transaction without explicitly specifying an isolation level. That usually means that System.Data.IsolationLevel.ReadCommitted will be used (I think the default isolation level can be set in the Sql Server settings somewhere). In that case the reader behaves the same as before. If I change it to use:
...
using (var trans = conn.BeginTransaction(System.Data.IsolationLevel.Serializable))
...
the insert of the "stop" record is blocked until the transaction is comitted. This means that while the reader is active, no changes to underlying the data is allowed by Sql Server.

How to read columns and rows with C#?

Hello I'm in need of a code to read columns and rows for C#.
I've come this far:
obj.MysqlQUERY("SELECT * FROM `players` WHERE name = "+name+";"); // my query function
Grateful for help;]
Here is a standard block of code that I use with MySql a lot. Note that you must be using the MySql connector available here.
string myName = "Foo Bar";
using (MySqlConnection conn = new MySqlConnection("your connection string here"))
{
using (MySqlCommand cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = #"SELECT * FROM players WHERE name = ?Name;";
cmd.Parameters.AddWithValue("Name", myName);
MySqlDataReader Reader = cmd.ExecuteReader();
if (!Reader.HasRows) return;
while (Reader.Read())
{
Console.WriteLine(GetDBString("column1", Reader);
Console.WriteLine(GetDBString("column2", Reader);
}
Reader.Close();
conn.Close();
}
}
private string GetDBString(string SqlFieldName, MySqlDataReader Reader)
{
return Reader[SqlFieldName].Equals(DBNull.Value) ? String.Empty : Reader.GetString(SqlFieldName);
}
Note that I am using a method to return a specific value if the database value is null. You can get creative and provide various return values or incorporate nullable types, etc.
Also you can use:
Create your own dataTable. When reader reaches end, you will have datatable which is custom created, and custom filled by yourself.
DataTable dt = new DataTable();
dt.Columns.Add("Id",typeof(int));
dt.Columns.Add("Name",typeof(string));
dt.Columns.Add("BlaBla",typeof(string));
dt.AcceptChanges();
// Your DB Connection codes.
while(dr.Read())
{
object[] row = new object[]()
{
dr[0].ToString(),// ROW 1 COLUMN 0
dr[1].ToString(),// ROW 1 COLUMN 1
dr[2].ToString(),// ROW 1 COLUMN 2
}
dt.Rows.Add(row);
}

Categories

Resources