0

So I'm running a query in ADO.NET that I know returns a single row. The relevant portion of my code looks like:

SqlCommand sqc = new SqlCommand();
sqc.Connection = new SqlConnection("connection string");
sqc.CommandText = "SELECT A, B FROM C WHERE D=@d";
sqc.Parameters.AddWithValue("@d", "d");
sqc.Connection.Open();
SqlDataReader rdr = sqc.ExecuteReader();
bool boolio = rdr.Read();
String a = (String)rdr["A"];

When I run this, I get an exception on the final line, complaining that I'm trying to read where there's no data.

When I step through this in VS I inspect rdr before the rdr.Read() line executes and I can see the data I want is sitting in the reader. Then I call rdr.Read() and the result is FALSE, indicating there is no more data for me to read. When I inspect rdr again, the internal ResultsView is empty.

I realize I can get my data simply by discarding the call to Read() BUT: this behaviour is contrary to the MSDN documentation which explicitly says "The default position of the SqlDataReader is before the first record. Therefore, you must call Read to begin accessing any data.". It is also contrary to every example of SqlDataReader usage I've found on the web such as this SO question which seems to exist only to taunt me.

Note that I've tested my code against .NET 3.5 and .NET 4.5 with identical results.

Am I missing something? Is there a bug in ADO.NET? Any input is appreciated!

UPDATE 1: The actual exception I get on the last line is:

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Data.dll

Additional information: Invalid attempt to read when no data is present.

UPDATE 2:

Because Nathan Skerl was unable to reproduce this behavior, and posted verified working code which subsequently failed for me, I suspect there may be an issue with the platforms we're running on. For the record I am using Windows 7 Professional (64-bit) Service Pack 1 and have compiled my project against .NET Framework 3.5 and 4.5 with the same results. If there is any relevant information I have failed to include please let me know and I will add it.

Community
  • 1
  • 1

2 Answers2

2

UPDATE: After some troubleshooting we realized the reader was empty due to inspecting the reader in the debugger by expanding the Results View (which calls to Read() and consumes the rows).

Using your code I ran this unit test in Linqpad and the results were as expected, the initial reader.read() advances to row "ONE", and the second call to row "TWO". Can you reproduce your error with a test such as this:

SqlCommand sqc = new SqlCommand();
sqc.Connection = (SqlConnection)this.Connection;
sqc.CommandText = "SELECT 'ONE' [A] union all select 'TWO' order by [A] asc;";
sqc.Parameters.AddWithValue("@d", "d");
sqc.Connection.Open();
SqlDataReader rdr = sqc.ExecuteReader();

bool boolio = rdr.Read();
String a = (String)rdr["A"];
a.Dump();

rdr.Read();
String b = (String)rdr["A"];
b.Dump();

Can you also show us what this pattern returns:

using (SqlConnection connection = (SqlConnection)this.Connection)
using (SqlCommand sqc = new SqlCommand("select 'ONE' as [A] union all select 'TWO';", connection))
{
   connection.Open();

   using (SqlDataReader rdr = sqc.ExecuteReader())
   {
       while (rdr.Read())
       {
          Console.WriteLine(String.Format("{0}", (String)rdr["A"]));
       }
   }
}
nathan_jr
  • 9,092
  • 3
  • 40
  • 55
  • I copy/pasted your code directly into my method in place of my own code. I changed the second line to get my actual connection and commented out the a.Dump() and b.Dump lines so it would compile. The behavior I described in my question occurred once again. That is to say, the line String a = (String)rdr["A"]; throws the same exception I was getting previously. –  May 21 '13 at 23:32
  • Because my code worked on your system, I am led to believe there may be some funny business going on regarding the platform. I'll update my question to indicate the version of everything I'm using. –  May 21 '13 at 23:33
  • After a litle more thought I figure that if it were an issue of the SqlDataReader starting on the first row rather than before it the exception would be thrown on the assignment to String b, not the assignment to String a... I will point out that when debugging before the first rdr.Read call I inspected rdr and *both* rows appeared in the ResultsView member, then after the first rdr.Read the ResultsView member was empty... –  May 21 '13 at 23:47
  • Very odd. The only ways I am able to repro the error is to exhaust the data by calling .Read() more than rows, or to forcibly close the connection. Can you please try the second pattern added above? I wonder if the oddity is in the debugger and not your code at all, ie your data seen in resultsview is not actually there by the time your assignment call? – nathan_jr May 21 '13 at 23:58
  • I just had the same thought as you regarding the debugger... the strangest part of this goes away when I stop inspecting rdr's values. Specifically, if I inspect the ResultsView of rdr before the first call to Read() then any rows that appeared there are gone after the call to Read(). I ran two test where I got the entire contents of my table (all code and database tables were identical in both runs), in the first I inspected rdr's ResultView and in the second I didn't. The first test result was all results vanished after Read() and the second result was that all rows but the first were there. –  May 22 '13 at 00:08
  • @Paul expanding results view will in fact call Read(), and consume your data reader. That would explain things! Reader is forward only cursor on the data, so once its read it gone :) – nathan_jr May 22 '13 at 00:09
  • Confirmed... everything works correctly if I don't inspect things. The part I don't understand is I originally started inspecting things because the exception I originally got popped up during a debugging session where I had no breakpoints in the method in question... –  May 22 '13 at 00:21
  • Perhaps in those instances you had an empty reader due to db call returning empty set. Its best to always check if rdr.Read() is true before attempting data access anyways. Glad we got that solved! – nathan_jr May 22 '13 at 00:22
  • It seems odd that the db call would have returned an empty set given that if I ran the query in SSMS I got the result I expected, and the DB server being queried is the same machine the code was running on. –  May 22 '13 at 00:26
  • I'm going to try more test cases to see if I can repro my original behavior... if I can't then I'm going to have to mark this as the answer. I appreciate all your assistance here, I just find the outcome a little odd considering I wasn't inspecting the Reader when the exception was first thrown. If that very first exception that started my inspecting was a "heisenbug", and the subsequent problems were all due to the inspecting... that's more irony than I can take in a day! –  May 22 '13 at 00:34
0

I have had the exact same problem trying to read-in 1 row, returned from a SP, called using ADO SQLCommand from Microsoft Dynamics 2013R2.

Specifically, I use this C/AL code to call my SP:

SQLCommand := SQLCommand.SqlCommand();
SQLCommand.CommandText := 'Custom_Item_Card_report_Get_Item_Qties'; //name of SP
SQLCommand.Connection := SQLConnection;
SQLCommand.CommandType := SQLCommand.CommandType.StoredProcedure;
SQLCommand.CommandTimeout := 0;

IF SQLCommand.Parameters.Count = 0 THEN BEGIN
SQLCommand.Parameters.AddWithValue('@CompanyName', COMPANYNAME);
SQLCommand.Parameters.AddWithValue('@ItemNo', InputItemCode);
SQLCommand.Parameters.AddWithValue('@FromDate', InputDateFrom);
SQLCommand.Parameters.AddWithValue('@Location', InputLocation);
END;

SQLReader := SQLCommand.ExecuteReader;
//IF SQLReader.Read() THEN BEGIN
rs1QtyCumulative := SQLReader.Item('QtyCumulative');
MESSAGE(FORMAT(rs1QtyCumulative));
//END;

Now, in my result-set I have just 2 rows (, by 3 columns decimal). Row 0 and row 1, let's call them.

And as stated in this issue, without the IF enclosure, it will give me the message with data from row 1, column 1 coming from the result-set of the SP.

With the IF and the .Read() it will give an error stating that a command to read data was issued where none exists. It is refering to the line of code before the MESSAGE.

SQLReader.Item('QtyCumulative');

This, as stated in this issue, constitutes a contradiction to the documentation that clearly states that the .ExecuteReader() will leave the SQLReader collection pointing to the row before the start of the data after it executes.

This most certainly does not happen and on top of that, if we do call the .Read() method for the SQLReader object, immediately after the call to .ExecuteReader() then it will move to the second row of the result-set.

Clearly they have some debugging to do with SqlDataReader objects.

scorpiophd
  • 105
  • 1
  • 6
  • for those that have this as an ease, they could select some values at the very top of their result-set, just to "make" 1 row of data and then union this row with the actual result-set. that's what i did to bypass the problems with row 0. – scorpiophd Jul 10 '14 at 14:33