views:

1136

answers:

6

Take a look at these two queries:

-- #1
SELECT * FROM my_table
WHERE CONTAINS(my_column, 'monkey')

-- #2
SELECT * FROM my_table
WHERE CONTAINS(my_column, 'a OR monkey')  -- "a" is a noise word

Query #1 returns 20 rows when I run it in Management Studio.
Query #2 returns the same 20 rows, but I also see the following in the Messages tab:

Informational: The full-text search condition contained noise word(s).

So far, so boring - exactly what I'd expect to happen.

Now, take a look at this C# snippet:

using (SqlConnection conn = new SqlConnection(...))
{
    SqlCommand cmd = conn.CreateCommand();
    // setup the command object...

    conn.Open();
    using (SqlDataReader dr = cmd.ExecuteReader())
    {
        if (dr.HasRows)
        {
            // get column ordinals etc...

            while (dr.Read())
            {
                // do something useful...
            }
        }
    }
}

When I run this code against query #1 everything behaves as expected - the "do something useful" section gets hit for each of the 20 rows.

When I run it against query #2, nothing happens - the "do something useful" section is never reached.

Now here's where things get a bit more interesting...

If I remove the HasRows check then everything works as expected - the "do something useful" section gets hit for each of the 20 rows, regardless of which query is used.

It seems that the HasRows property isn't populated correctly if SQL Server generates a message. The results are returned and can be iterated through using the Read() method but the HasRows property will be false.

Is this a known bug in .NET and/or SQL Server, or have I missed something obvious?
I'm using VS2008SP1, .NET3.5SP1 and SQL2008.

EDIT: I appreciate that my question is very similar to this one, and it's almost certainly a manifestation of the same issue, but that question has been bogged down for three months with no definitive answer.

A: 

That certainly is strange behavior - but I am wondering why you need to check HasRows if you are going to simply iterate the result-set.

The HasRows property encapsulates a _hasRows field which gets set in to true or false inside SqlDataReader in many different places for many different reasons. Most of these places it is set to true if the private TdsParserStateObject's PeekByte method returns a number that indicates data is present.

Andrew Hare
I check HasRows and then use GetOrdinal to grab the column ordinals before iterating through the results.The real code is called hundreds of times per second and iterates thousands of rows, so I don't really want unnecessary GetOrdinal calls for every iteration, or when no results are returned.
LukeH
A: 

As with Andrew I am not sure why you wouldn't just use the while loop, to avoid the performance hit of additional GetOrdinal calls. you could use a flag value to do the GetOrdinal calls on first row, then after that the code can be skipped.

I have noticed similar issues with HasRows in the past, and have gone to a pattern similar to mine listed above with minimal issues.

Mitchel Sellers
I agree with you and Andrew that the HasRows check isn't strictly necessary and that there are other ways to do the GetOrdinal calls without incurring a performance penalty, but that's not really the point!
LukeH
@Luke - I have actually noticed the behavior that you are noting in a SQL2005 installation that I have as well. As for is it a "known" issue, that I am not sure of
Mitchel Sellers
+1  A: 

Sounds like HasRows is one of those properties which value is not guaranteed to be accurate...

I agree with the previous two posts (about going straight into while(dr.Read()) and get the ordinals in the first iteration). Also, why not get a dataset instead of a data reader? If in this case you're only dealing with 20 rows, getting the whole dataset at once might not be much of a performance hit compared to using a data reader. I know this doesn't really answer your question, but just a thought for a workaround.

Ricardo Villamil
Maybe, but I can't find anything in the documentation saying that the accuracy of HasRows can't be guaranteed. The actual query returns far more than 20 rows and performance is important, hence the DataReader rather than a DataSet.
LukeH
A: 

Please could you try the following and report if it was successful or otherwise:

using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.SingleResult))

Thanks

GateKiller
No luck, changing the CommandBehavior makes no difference.
LukeH
Ok, thanks for testing.
GateKiller
+1  A: 

Hi, I'm the original poster of the refernced question (lost login) and never managed to figure it out. In the end I put it down to bad voodoo, sacrificed neatness and went with something like

bool readerHasRows=false;
while(reader.reader())
{
   readerHasRows=true;
   doStuffOverAndOver();
}
if (!readerHasRows)
{
   probablyBetterShowAnErrorMessageThen();
}

What was really weird was that it worked in one aspx page and not in a another despite the code blocks being almost identical bar the stored procedure used.

Needless to say I'm avoiding .HasRows from now on ;)

EDIT - Management Studio shows messages in the messages tab on the problem procedure in my project too. So that seems to be the cause of the problem. But why would it bugger up .HasRows??

EDIT2 - Confirmed, altered the query to avoid the warning messages and .hasrows is now true.

ctrlalt3nd
Thanks for confirming that. I definitely consider this to be a bug in HasRows rather than a problem with our code. (There's certainly no documentation which suggests that HasRows should behave in this way.)
LukeH
Agreed. It's called HasRows, not HasRowsAndNoWarnings.
ctrlalt3nd
A: 

I'd suggest calling the NextResult method if multiple result sets are suspected to be causing this problem. Since the first result set is the one that appears to be empty, using CommandBehavior.SingleResult would not change the behavior, since the first (empty) result would still be returned. You could try that. Anyway, I have heard this was a bug, but I don't remember where I read it, and a quick Google search didn't yield any results.

Triynko