Difference between SqlDataReader.Read and SqlDataReader.NextResult - c#

What is the main difference between these two methods? On the msdn website it is explained like below but I don't understand it.
Read Advances the SqlDataReader to the next record. (Overrides
DbDataReader.Read().)
NextResult Advances the data reader to the next
result, when reading the results of batch Transact-SQL statements. (Overrides dbDataReader.NextResult().)

If your statement/proc is returning multiple result sets, For example, if you have two select statements in single Command object, then you will get back two result sets.
NextResult is used to move between result sets.
Read is used to move forward in records of a single result set.
Consider the following example:
If you have a proc whose main body is like:
.... Proc start
SELECT Name,Address FROM Table1
SELECT ID,Department FROM Table2
-- Proc End
Executing the above proc would produce two result sets. One for Table1 or first select statement and other for the next select statement.
By default first result set would be available for Read. If you want to move to second result set, you will need NextResult.
See: Retrieving Data Using a DataReader
Example Code from the same link: Retrieving Multiple Result Sets using NextResult
static void RetrieveMultipleResults(SqlConnection connection)
{
using (connection)
{
SqlCommand command = new SqlCommand(
"SELECT CategoryID, CategoryName FROM dbo.Categories;" +
"SELECT EmployeeID, LastName FROM dbo.Employees",
connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.HasRows)
{
Console.WriteLine("\t{0}\t{1}", reader.GetName(0),
reader.GetName(1));
while (reader.Read())
{
Console.WriteLine("\t{0}\t{1}", reader.GetInt32(0),
reader.GetString(1));
}
reader.NextResult();
}
}
}

Not strictly an answer to this question but if you use the DataTable.Load method to consume data from the reader rather than Reader.Read note that after the Load method has completed, the reader is now placed at the start of the next result set so you should not call the NextResult method otherwise you will skip the next resultset.
A simple loop on Reader.HasRows around a DataTable.Load call is all that you need to process potential multiple resultsets in this scenario.

Related

SqlDataReader Reader.Read() shows Enumeration yielded no results

I am Trying to generate random Ids from a given table. I can see the random number generated in debug but when I reach to reader.Read() line it shows Enumeration yielded no results.
I couldn't quite get what I am missing.
private static void GetRandomId(int maxValue)
{
string connectionString =
"Data Source=local;Initial Catalog=Test;user id=Test;password=Test123;";
string queryString = #"SELECT TOP 1 Id from Pointer WHERE Id > (RAND() * #max);";
using (var connection = new SqlConnection(connectionString))
{
var command = new SqlCommand(queryString, connection);
command.Parameters.AddWithValue("#max", maxValue);
connection.Open();
using (var reader = command.ExecuteReader()) <-- // Here I can see the randon value generated
{
while (reader.Read())
{
//Here reader shows : Enumeration yielded no results
Console.WriteLine("Value", reader[1]);
reader.Close();
}
}
}
}
Since you are basically searching for a random Id of an existing record, I believe this may cover what you are trying to do:
Random record from a database table (T-SQL)
SELECT TOP 1 Id FROM Pointer ORDER BY NEWID()
Use SqlCommand.ExecuteScalar Method instead
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar%28v=vs.110%29.aspx
var dbRandomId = command.ExecuteScalar();
var randomId = Convert.IsDbNull(dbRandomId) ? (int?)null : (int)dbRandomId;
// you now also know if an id was returned with randomId.HasValue
https://msdn.microsoft.com/en-us/library/system.convert.isdbnull%28v=vs.110%29.aspx
Issues with your example:
Issue 1: Couldn't you have #max be computed with a SELECT #max = MAX(Id) FROM Pointer? No need to pass it in a parameter. Or am I missing the point? Is that a deliberate limit?
Issue 2: Shouldn't it be reader[0] or reader["Id"]? I believe columns are zero based and your selected column's name is "Id".
Issue 3: Be careful not to enumerate somehow the reader via the Debugger because you'll actually consume (some of?) the results right there (I'm guessing you are doing this by your comment "// Here I can _see_ the random value generated") and by the time the reader.Read() is encountered there will be no results left since the reader has already been enumerated and it won't "rewind".
https://msdn.microsoft.com/en-us/library/aa326283%28v=vs.71%29.aspx
DataReader cursor rewind
Issue 4: Why do you close the reader manually when you've already ensured the closing & disposal with using? You also already know it's going to be one record returned (the most) with the TOP 1.
If you check the results of the sqlDataReader in the debugger, the results are then gone, and wont be found by the Read() event

Multiple queries on same connection using same T-SQL variables

I'm trying to create something similar to SQL Server Management Studio.
I want the ability to run several queries on the same connection like one does in the same tab in SSMS, however from c# each SqlCommand is executed separately even with the same SqlConnection so they can't see each others DECLARE's
Example: If you run two queries:
DECLARE #ted varchar(100) = 'Ted A';
SELECT #ted as [Query1];
and the other
SELECT #ted as [Query2];
If you run these two, in order, separately in a SSMS tab, you should get two correct results, one from each query.
However the problem I have is if I have one SqlConnection and I create two SqlCommand objects then they don't see the #ted variable even though they are the same connection.
How can I create a connection to a sql server database so that it behaves like an SSMS tab in that each subsequent query is in the same scope, and therefore I can use #variables in all queries?
Note: the same behavior as in SSMS can also be found in the SQLCMD utility.
Edit: i have just realised what i have asked is not how SSMS works, this is actually impossible.
you should be able to access #tables etc from the same connection but not #variables
You should think of a single query window in SSMS as roughly equivalent to a single SQLCommand, and a single instance of SSMS as roughly equivalent to a single SqlConnection. TSQL Variables have batch scope not connection scope.
A SQLCommand executes a batch of TSQL, possibly including multiple executable statements. You can simple put both of your queries into a single command.
Now, if you want your command to return multiple values from multiple select statements, you would use the NextResult method of the SqlDataReader. This will move to the result set of the next statement. Process the results of the each statement, then use NextResult to interate through the result sets.
Simple example
queryText = "Declare #Ted Varchar(100) = 'Ted A'; SELECT #ted --Or some other query using #ted; Select #ted --or yet another query using #ted;"
command = new SqlCommand(queryText);
reader = command.ExecuteReader();
do
{
//Result set handling code goes here
do
{
//Record level handling code goes here
} while reader.Read();
//More result set handling code goes here
} while reader.NextResult();
Hope this helps
The DECLARE is scoped to a command and not a connection. If you want to reuse declares then you will need to utilize a T-SQL parser.
Otherwise you can get started with something like this:
var commandText =
"DECLARE #ted varchar(100) = 'Ted A';" +
"SELECT #ted as [Query1];" +
"SELECT #ted as [Query2];";
using(var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = new SqlCommand(commandText, connection))
{
using(var reader = command.ExecuteReader())
{
// There are two result sets and each result set has one result.
do
{
// You will need to use the Schema Table to dynamically
// generate the results view
var schema = reader.GetSchemaTable();
// "ColumnName" column will have the "Query1" and "Query2"
var columnNameColumn = schema.Columns["ColumnName"];
var row = schema.Rows[0][columnNameColumn];
Console.WriteLine(row);
// Now we write the results
while(reader.Read())
{
Console.WriteLine(reader.GetString(0));
}
}
while(reader.NextResult());
}
}
}

Get number of result sets from a SqlDataReader

I have an SQL Server stored procedure that returns multiple results. The body of the stored procedure might look like this:
SELECT * FROM tableA;
SELECT * FROM tableB;
SELECT * FROM tableC;
In that case, the stored procedure returns 3 result sets. Other stored procedures might return, e.g., 1, 0, or any number of result sets. Each result set might contain 0 or more rows in it. When loading these, I will need to call IDataReader.NextResult() to navigate between result sets.
How can I reliably get the count of result sets (not row counts) in C#?
There seems to be no property or method that directly calculates the result count in IDataReader. This interface rather intends to be consumed in an incremental/streaming fashion. So, to count the number of result sets returned, increment a counter every time you call IDataReader.NextResult() and it returns true while consuming the data.
However, there is a catch. The
The documentation for IDataReader.NextResult() states:
By default, the data reader is positioned on the first result.
Consider the following scenarios:
The command returned 0 result sets. Your first call to IDataReader.NextResult() returns false.
The command returned 1 result set. Your first call to IDataReader.NextResult() returns false.
The command returned 2 result sets. Your second call to IDataReader.NextResult() returns false.
You can see that we have enough information to count the number of result sets as long as there is at least one result set. That would be the number of times that IDataReader.NextResult() returned true plus one.
To detect whether or not there are 0 result sets, we use another property from the reader: IDataRecord.FieldCount. The documentation for this property states:
When not positioned in a valid recordset, 0; otherwise, the number of columns in the current record. The default is -1.
Thus, we can read that field when first opening the reader to determine if we are in a valid result set or not. If the command generates no result sets, the value of IDataRecord.FieldCount on the reader will initially be less than 1. If the command generates at least one result set, the value will initially be positive. This assumes that it is impossible for a result set to have 0 columns (which I think you can assume with SQL, not sure).
So, I would use something like the following to count the number of result sets. If you also need to save the data, that logic must be inserted into this:
using (var reader = command.ExecuteReader())
{
var resultCount = 0;
do
{
if (reader.FieldCount > 0)
resultCount++;
while (reader.Read())
{
// Insert logic to actually consume data here…
// HandleRecordByResultIndex(resultCount - 1, (IDataRecord)reader);
}
} while (reader.NextResult());
}
I’ve tested this with System.Data.SqlClient and the commands PRINT 'hi' (0 result sets), SELECT 1 x WHERE 1=0 (1 result set), and SELECT 1 x WHERE 1=0; SELECT 1 x WHERE 1=0 (2 result sets).
Use DataReader.NextResult to advance the reader to the next result set.:
using (var con = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
using (var cmd = new SqlCommand("SELECT * FROM TableA; SELECT * FROM TableB; SELECT * FROM TableC;", con))
{
con.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
// other fields ...
}
if (rdr.NextResult())
{
while (rdr.Read())
{
int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
// other fields ...
}
if (rdr.NextResult())
{
while (rdr.Read())
{
int firstIntCol = rdr.GetInt32(0); // assuming the first column is of type Int32
// other fields ...
}
}
}
}
}
}
Another solution to be aware of, in addition to the manual SqlDataReader method, in the accepted answer, is to use a SqlDataAdapter and DataSets and DataTables.
When using those classes, the entire result set is retrieved from the server in one go, and you can iterate them at your leisure. Also, several other .net classes are aware of DataSets and DataTables and can be hooked up to them directly, for read-only or read-write data access, if you also set the DeleteCommand, InsertCommand, and UpdateCommand properties. What you get "for free," with that, is the ability to alter the data in the DataSet, and then simply call Update() to push your local changes to the database. You also gain the RowUpdated event handler, which you can use to make that happen automatically.
DataSets and DataTables retain the metadata from the database schema, as well, so you can still access columns by name or index, as you please.
Overall, they're a nice feature, though certainly heavier weight than a SqlDataReader is.
Documentation for SqlDataAdapter is here: https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldataadapter

How to know if a query succeeded?

How can I know if the following INSERT query succeeded?
MySqlConnection connection = new MySqlConnection(ConnectionString);
connection.Open();
MySqlCommand command = new MySqlCommand("INSERT INTO People (PersonName, dateAdded) VALUES (?name, NOW())", connection);
command.Parameters.Add(new MySqlParameter("name", Name));
MySqlDataReader Reader = command.ExecuteReader();
I tried running Reader.Read() but only an empty string is returned. Thanks.
You instead use ExecuteNonQuery:
int affectedRows = command.ExecuteNonQuery();
if (affectedRows <= 0)
{
// Houston, we have a problem.
}
Keep in mind if you have triggers on the data, it may not reflect the actual number of rows you attempted to INSERT:
You can use ExecuteNonQuery to perform any type of database operation, however any resultsets returned will not be available. Any output parameters used in calling a stored procedure will be populated with data and can be retrieved after execution is complete. For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. For all other types of statements, the return value is -1.
In general, however, you will receive a MySqlException if your query encounters an error.
Don't use ExecuteReader if you're doing an INSERT, UPDATE or DELETE. Use ExecuteNonQuery instead, it'll return the number of affected rows.
See http://dev.mysql.com/doc/refman/5.6/en/connector-net-tutorials-intro.html#connector-net-tutorials-sql-command
Only use the SqlDataReader when you're queryng the data base, the SqlDataReader is a fast forward-only pointer, so only is recommended for Querying, use the ExecuteNonQuery() method of the SqlCommand, for example as the next code:
using (SqlConnection conn = new SqlConnection("[QueryString]")) {
using (SqlCommand cmd = new SqlCommand("SQL Command",conn)) {
//here you return the number of affected rows
int a = cmd.ExecuteNonQuery();
}
}
I hope, this could help you.
An exception will be thrown if it fails. The exception object will typically contain information about what went wrong (e.g. "primary key violation" etc...).
Also, use ExecuteNonQuery for INSERT, UPDATE or DELETE. The returned value will contain the number of affected rows.

c# ExecuteNonQuery always returns Zero

I think nothing's wrong with the connection because when I open it, it does not throw any error. So I guess the error is when I'm executing a command. This is my code:
OleDbCommand cmd = new OleDbCommand("SELECT * FROM cars", conn);
cmd.CommandType = CommandType.Text;
int numbers = cmd.ExecuteNonQuery();
I tried using a message box to display the value of numbers but the result is always 0. The table cars contains 5 records. So why am I not getting the correct results?..
To the anonymous downvoter, the key part of the OP:
I tried using a message box to display the value of numbers but the
result is always 0. The table cars contains 5 records. So why am I not
getting the correct results?..
The OP is obviously trying to get a count of records in the table (a scalar aggregate) and not all of the table data.
My answer:
That's because your query is returning a table and not a scalar value and you're calling the incorrect function. Your query should be should be:
"SELECT COUNT(*) FROM cars"
And ExecuteNonQuery doesn't actually expect any results to be returned. (You usually run insert, update and delete operations with ExecuteNonQuery.) You should be using ExecuteScalar which expects a single-valued result such as count(*).
All together now:
OleDbCommand cmd = new OleDbCommand("SELECT count(*) FROM cars", conn);
cmd.CommandType = CommandType.Text;
int numbers = cmd.ExecuteScalar();
Try using ExecuteScalar that should give you the count. ExecuteNonQuery doesn't return the results from your query. The return your looking at indicates how many rows were affected by your statement, in your case zero.
ExecuteNonQuery as the name tells you does not make a query. it is normally used for inserts or updates and returns the number of affected records. for the query you provided you should use ExecuteReader or a DataAdapter and its Fill method to fill a datatable.
Because you are executing a query, and ExecuteNonQuery returns the number of rows effected, which when you select is always 0 since you aren't changing anything (ie. INSERT,UPDATE or DELETE)
ExecuteNonQuery only returns the number of rows affected for UPDATE, DELETE or INSERT operations. For the number of rows in the SELECT statement, try:
OleDbCommand cmd = new OleDbCommand("SELECT * FROM cars", conn);
OleDbDataReader reader = cmd.ExecuteReader();
int numbers = 0;
while (reader.Read())
{
//// other Reader-related operations here...
numbers++;
}

Categories