C# & ADO.NET

SqlCommand.ExecuteReader example in ASP.NET (C#)

C# & ADO.NET · · Published 2026-10-08

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

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