The following post relates to the System.Data.SQLite data provider by phxsoftware (http://sqlite.phxsoftware.com)
I have a question (and possibly a problem) with DbDataReader’s Read method and/or Visual Studio 2008. In many examples I see things like the following (and I know this code doesn't make a lot of sense ... but it serves a purpose):
DbDataReader reader = null;
Long ltemp = 0;
lock (m_ClassLock)
{
DbCommand cmd = dbCnn.CreateCommand();
cmd.CommandText = “SELECT col1 FROM table1”;
reader = cmd.ExecuteReader();
if (null != reader)
{
while (reader.Read())
{
ltemp += (long)reader[0];
}
}
reader.Close();
First question - What I dont understand from this example is am I missing data the first time through the while loop by calling reader.Read() upfront? For instance, if the reader has values (3,5,7,9) the returned reader from cmd.ExecuteReader() should be pointing at 3 initially, correct? reader.Read() would then move to 5, 7, and 9 on subsequent invocations within the while loop. But, because reader.Read() is invoked before the first "ltemp += ..." line am I skipping past the first result (3)?
Second question - (and I'm starting to think this might be a bug in VS) If I step through this set of code in the debugger when I stop at a breakpoint on the "if (null != ..." line I can clearly see mu mousing over and drilling down in the popup that reader has multiple row data values assigned to it. However, if I close that popup information, and then try to bring it back up, when I drill down I now see the line "Enumeration yielded no results" where there was clearly data before.
Can anyone explain this behavior?
Think about it like this after you run ExecuteReader the set is on row -1. You need to execute Read to get to row 0.
IDataReader is a forward only structure, you can only iterate through it once, the debugger is iterating through it.
General questions:
Why the lock?
Why the null check for reader - I am not aware of any issues where ExecuteReader return null after a select.
Why not "SELECT SUM(col1) from table1
Why are you not following the dispose pattern?
Related
After fighting with this for some time I tried even this very degenerate example (the correct one should be "SHOW VARIABLES like 'max_allowed_packet'") and it still fails:
String CommandText = "Show variables;";
MySqlCommand Command = new MySqlCommand(CommandText, Connection);
using (MySqlDataReader Reader = Command.ExecuteReader())
while (Reader.Read())
return (int)Reader["Value"];
^c^ving the command text into the workbench produces the expected result for every query I have tried. No matter what syntax I use for the show variables I get "Enumeration yielded no results" in the reader and of course it crashes trying to cast that to an integer. Immediately before hitting this code there was a Change Database command done with the connection so it must be open.
I can't think of anything that could be wrong here that would allow the query to execute at all (it does figure out the field count should be 2 so it must have talked to the database) and yet not give me any results.
The suggested question certainly looks like mine but I already tried that case:
Select * from information_schema.global_variables where variable_name like 'max_allowed_packet'
and got the same result--nothing. As before, copying the text out of the debugger and pasting it in the workbench yields the right result. How can this one situation produce no results without producing a total failure?
SHOW GLOBAL VARIABLES; should work with 5.6.17 or greater versions
or SHOW LOCAL VARIABLES if you are looking for session variables.
Interesting behaviour has been noticed by me recently.
When having MS SQL stored-procedure ran using SqlCommand.ExecuteScalar(), my application seems to be completely unaware to any SQL Errors or PRINTs which appear after SELECT is done.
Most probable explanation is that flow control is given to C# immediately after any SELECT result appears, without waiting stored procedure to finish (though stored procedure continues execution silently underneath).
Obvious advantage is performance gain (no need to wait, since the result is already known), unfortunately C# app is unaware of any SQL exceptions that could happen after that point.
Could anyone confirm my explanation? Could this behaviour be altered?
The ExecuteNonQuery method will call "ExecuteReader" and immediately call "Close" on the returned reader object. ExecuteScalar will call "Read" once, pick out the first value (index 0) and then call "Close".
Since the DataReader is essentially nothing more than a specialized network stream, any information that is returned afther it's current location (when Close is called) will just never reach the actual client components, even though the server might have sent it. The implementation is as such to avoid returning a huge amount of data when none is required.
In your case, I see two solutions to this problem.
make sure that you use ExecuteReader instead, and read all the way through the result:
using(var reader = command.ExecuteReader())
{
do
{
while (reader.Read()) { /* whatever */ };
} while (reader.NextResult());
}
If you can control the server side, it will help to move the actual "send-to-client" select to the end of the procedure or batch in question. Like this:
create proc Demo
as
declare #result int
select top 1 #result = Id from MyTable where Name = 'testing'
print 'selected result...'
select #result Id -- will send a column called "Id" with the previous value
go
I haven't used DataReaders in ages (I prefer to use an ORM) but I'm forced to at work. I pull back the rows, and check that HasRows is true; debugging at this point and examining the reader shows that my data is there.
Now here's the issue: the moment I call reader.Read(), trying to expand the results says "The enumeration yielded no results" or whatever, and I get the "Invalid attempt to read when no data is present." error. I get the same thing if I don't call Read() (which is the default since the DataReader starts before the first record).
I cannot remember the proper way to handle this; the data is there when I check HasRows, but is gone the moment I either try to read from it right after or after I call Read, which makes no sense as if I don't call Read, the reader should still be before the first record, and if the property is set that starts it at the first record (SingleRow? I forget the name of it) is set, then I should be able to read rows without calling Read, however both ways seem to move past the row containing the data.
What am I forgetting? Code is fairly straightforward:
TemplateFile file = null;
using (DbDataReader reader = ExecuteDataReaderProc("GetTemplateByID", idParam))
{
if (reader.HasRows) // reader has data at this point - verified with debugger
{
reader.Read(); // loses data at this point if I call Read()
template = new TemplateFile
{
FileName = Convert.ToString(reader["FileName"]) // whether or not I call
// Read, says no data here
};
}
}
Just to clarify the answer, it was using the debugger since expanding the results view calls Read() and therefore it moves past the row. As Marc Gravell said in a comment: Debugger considered harmful
If you want to put the data into a file, start by loading a DataTable instead of using a DataReader.
With the DataReader, as has been mentioned in the comments, you might want to iterate through the result set with a while loop
while (reader.Read())
{
}
The loop reads one row at a time and quits when all of the rows have been read.
Once you move to the next row, the previous rows are no longer available unless you have put them into some other structure, like a list or DataTable.
But you can use a DataAdapater to fill a DataTable so there might not be a reason to use a DataReader. Then you can write to a file from the DataTable.
In any event, I don't see how this line could work.
FileName = Convert.ToString(reader["FileName"])
I can post additional code for either approach if you like.
HTH Harvey Sather
I am retrieving 10 rown from my database but I want to skip the first one. Reason being that the first item in my table is already displayed within the main div on my page. Now I want to list all the other remaining records underneath it. How do I accomplish this?
My code works ok and I can display all records from the reader. All I need now is how to skip the first one.
Just read the first one, then continue with the others:
myReader.Read();
while(myReader.Read())
{
//do stuff
}
If you like to use Linq, here's a trick to make it work with DataReaders, using a simple extension method :
public static IEnumerable<IDataRecord> AsEnumerable(this IDataReader reader)
{
while (reader.Read())
{
yield return reader;
}
}
You can then use the Linq Skip method :
using (var reader = command.ExecuteRead())
{
foreach(var row in reader.AsEnumerable.Skip(1))
{
// whatever you do with the data...
}
}
Just call reader.Read() one extra time to start with, to advance to the next record. Then treat the reader as normal. You may want to still check the value of reader.Read() from that first call - if it returns false then there wasn't even the first record you were expecting.
I can't help thinking that there's got to be something else "wrong" when you require skipping the first row in your DataReader like:
Maybe your SQL query, sproc or whatever should exclude the first row instead?
Why do you already have your first row? Maybe you could get all the rows at the same time?
Your question implies a lack of coding experience - either in general or with regards to DataReader or something. Maybe you could get even more help if you explained why you need to skip the first row?
When you use a SqlDataReader, is the return set completely determined by the ExecuteReader step, or can you influence what you get by writing to the source table(s) while reading? Here is an example in very rough pseudo code.
sc = new SqlCommand("select * from people order by last, first",db) ;
sdr = sc.ExecuteReader() ;
while (sdr.read())
{
l = (string) sdr["last"] ;
k = (string) sdr["key"] ;
if (l.Equals("Adams"))
{
sc2 = new SqlCommand("update people set last = #nm where key = #key") ;
sc2.Parameters.Add(new SqlParameter("#nm", "Ziegler"));
sc2.Parameters.Add(new SqlParameter("#key", k));
sc2.ExecuteNonQuery() ;
}
}
I've seen a lot of bad errors in other environments caused by writing to the table you are reading. Here record k gets bumped from the top of the list (Adams) to the bottom (Ziegler). I've assumed (ha!) that SqlDataReader is immune. True? False?
It depends on your transaction isolation level or other locking hints, but iirc by default reading from a table in sql server locks those records, and therefore the code you posted will either deadlock (sc2 will eventually timeout) or the updates will go into the the transaction log and none will be written until your reader is finished. I don't remember which off the top of my head.
One issue I see is that when the reader is open it owns the database connection, nothing else can use it while the reader is open. So the only way possible to do this is using a different database connection, and still it would depend on transaction level
If you want to assume that the read data is not altered by those updates, could you read the data into a temporary object container, and then after all the reading is done, then do your updates? It would make the issue moot.
Of course, I did find the question interesting from a "how does this really work" standpoint.
If you want to do updates while you're iterating on the query results you could read it all into a DataSet.
I know you didn't ask about this, and I also know this is pseudo-code, but be sure to wrap your sc, sdr, and sc2 variables in using () statements to ensure they're disposed properly.