dr.Read() loops to the next record and returns true or false depending on if there's a record to be read or not. When the data reader is initialized, the first record is not selected. It has to be selected by calling dr.Read() which will then return true if a first row is found, and indeed will return true until Read() is called when currently on the last row - i.e. there's no more rows left to read.
dr.HasRows is just a property that tells you if the datareader contains rows... which if it does will keep having rows from here until eternity. For instance, you would use this if you wanted to do something in the event it has rows and something else in the event it doesn't:
if (dr.HasRows)
{
while (dr.Read())
{
}
}
else
{
Console.WriteLine("I didn't find any relevant data.");
}