SqlCommand.ExecuteReader example in ASP.NET (C#)
ExecuteReader returns a SqlDataReader: a fast, forward-only stream of rows. You call Read() until it returns false and pick values from each row. This example reads a list of upcoming events, then shows two extras: several result sets in one round trip and a single-row lookup.
Reading rows into a list
public class EventItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime StartsAt { get; set; }
public string Venue { get; set; } // nullable column
}
public static List<EventItem> GetUpcoming(string cs, int max)
{
const string sql = @"SELECT TOP (@max) EventId, Title, StartsAt, Venue
FROM dbo.Events
WHERE StartsAt >= SYSUTCDATETIME()
ORDER BY StartsAt";
var list = new List<EventItem>();
using (var con = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.Add("@max", SqlDbType.Int).Value = max;
con.Open();
using (SqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
list.Add(new EventItem
{
Id = r.GetInt32(0),
Title = r.GetString(1),
StartsAt = r.GetDateTime(2),
Venue = r.IsDBNull(3) ? null : r.GetString(3)
});
}
}
}
return list;
}
Typed getters (GetInt32, GetString…) must match the column's SQL type; reading an int column with GetInt64 throws InvalidCastException. Check IsDBNull first on every nullable column.
Showing the result on the page
<asp:Repeater ID="rptEvents" runat="server" ItemType="Demo.EventItem">
<HeaderTemplate><ul class="events"></HeaderTemplate>
<ItemTemplate>
<li><time datetime='<%# Item.StartsAt.ToString("s") %>Z'><%# Item.StartsAt.ToString("d MMM yyyy HH:mm") %></time>
<%#: Item.Title %> <%#: Item.Venue ?? "" %></li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
rptEvents.DataSource = GetUpcoming(ConfigurationManager.ConnectionStrings["Db"].ConnectionString, 10);
rptEvents.DataBind();
}
}
<%#: … %> encodes the text, so a title containing <script> is shown, not executed. Never build HTML by concatenating reader values without encoding.
Several result sets in one call
const string sql = @"SELECT COUNT(*) FROM dbo.Events WHERE StartsAt >= SYSUTCDATETIME();
SELECT TOP (5) Title FROM dbo.Events ORDER BY CreatedAt DESC;";
using (var con = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, con))
{
con.Open();
using (SqlDataReader r = cmd.ExecuteReader())
{
r.Read();
int upcoming = r.GetInt32(0);
r.NextResult(); // move to the second SELECT
var latest = new List<string>();
while (r.Read()) latest.Add(r.GetString(0));
}
}
One row only
using (SqlDataReader r = cmd.ExecuteReader(CommandBehavior.SingleRow))
{
if (r.Read())
{
// found
}
}
CommandBehavior.SingleRow tells the provider you expect at most one row, which can save work. Other useful flags: CloseConnection (closing the reader closes the connection) and SequentialAccess (for large binary or text columns read as streams, in column order).
Things to avoid
- Keeping a reader open while running another command on the same connection: SQL Server refuses unless the connection string enables
MultipleActiveResultSets=True. Usually it is better to finish reading first. - Returning an open reader from a method without
CommandBehavior.CloseConnection: the connection leaks back to nowhere. - Selecting
*: column positions change when the table changes, and ordinal getters then read the wrong data.
Binding a reader straight to a grid is shown in Bind a GridView with ExecuteReader; turning it into a table in Convert a SqlDataReader to a DataTable.
Official documentation
- Microsoft Learn: SqlCommand.ExecuteReader, SqlDataReader.NextResult, CommandBehavior
- Microsoft Learn: Retrieving data using a DataReader