ADO.NET · Executing Commands

SqlDataReader

SqlDataReader streams rows from SQL Server one at a time. It is the fastest way to read data in .NET.

Intermediate 5 min Lesson 10 of 32Examples use Microsoft.Data.SqlClient on .NET 9 with SQL Server 2022.
Before you start:SqlCommand
0/32 in ADO.NET
On this page · 5 min · Intermediate

In simple words#

A SqlDataReader is a ticket counter queue: rows come one at a time, forward only. Read each, then move to the next — fast and light.

Read()
Move to the next row; false at the end.
GetInt32 / GetString
Typed getters for columns.
Forward-only
Cannot go back to earlier rows.

Learning objectives#

  • Loop through rows with Read/ReadAsync
  • Read columns with typed getters
  • Use GetOrdinal for clear, fast column access
  • Map rows into C# records

Explanation#

A reader is forward-only and read-only: you move from the first row to the last, once.

Typed getters (GetInt32, GetString, GetDecimal, GetDateTime) avoid boxing and conversions. GetFieldValue<T> works for any type.

Look up column positions once with GetOrdinal("Name") before the loop: clearer than magic numbers and faster than reader["Name"] on every row.

Check IsDBNull before reading nullable columns.

Code example: Mapping rows to records#

C#ProductReader.cs Illustrative
public record Product(int Id, string Name, decimal Price, int Stock, string? Description);


static async Task<List<Product>> GetProductsAsync(string cs)
{
    await using var conn = new SqlConnection(cs);
    await conn.OpenAsync();
    await using var cmd = new SqlCommand("SELECT Id, Name, Price, Stock, Description FROM Products ORDER BY Name", conn);
    await using var r = await cmd.ExecuteReaderAsync();


    int id = r.GetOrdinal("Id"), name = r.GetOrdinal("Name"), price = r.GetOrdinal("Price"),
        stock = r.GetOrdinal("Stock"), desc = r.GetOrdinal("Description");


    var list = new List<Product>();
    while (await r.ReadAsync())
    {
        list.Add(new Product(
            r.GetInt32(id), r.GetString(name), r.GetDecimal(price), r.GetInt32(stock),
            r.IsDBNull(desc) ? null : r.GetString(desc)));
    }
    return list;
}


foreach (var p in await GetProductsAsync(cs)) Console.WriteLine(
quot;
{p.Name,-10} ₹{p.Price,9} stock {p.Stock}");
Expected output
Keyboard   ₹  2200.00 stock 40
Laptop     ₹ 55000.00 stock 8
Mouse      ₹  1400.00 stock 120

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. GetOrdinal finds each column’s position once.
  2. ReadAsync moves to the next row without blocking a thread.
  3. Typed getters read values directly.
  4. Description can be NULL, so IsDBNull decides between null and the string.
  5. The method returns a list — the reader and connection are closed before it returns.

Real-world use case#

A Web API GET /products endpoint uses this method; the controller returns the list as JSON.

Common mistakes#

Calling GetString on a NULL column

It throws SqlNullValueException. Check IsDBNull first.

Wrong getter for the SQL type

GetInt32 on a BIGINT or GetDouble on a DECIMAL throws InvalidCastException. Match types: INT→GetInt32, BIGINT→GetInt64, DECIMAL→GetDecimal.

Best practices#

  • Use GetOrdinal once, outside the loop
  • Use typed getters
  • Map to records or DTOs inside the method
  • Select only the columns you use

Remember this#

Forward-only, read-only, fastest.

GetOrdinal once → typed getters in the loop.

IsDBNull before reading nullable columns.

Practice exercise#

Your task: Read Id and Name of products with Price above a given amount into a List<(int, string)>.

Show solution
C# Illustrative
await using var cmd = new SqlCommand("SELECT Id, Name FROM Products WHERE Price > @min", conn);
cmd.Parameters.Add("@min", SqlDbType.Decimal).Value = min;
await using var r = await cmd.ExecuteReaderAsync();
var result = new List<(int, string)>();
while (await r.ReadAsync()) result.Add((r.GetInt32(0), r.GetString(1)));

Mini quiz#

SqlDataReader quizMultiple choice Question 1 of 2

Which getter reads a SQL DECIMAL column?

Score so far: 0/0

Want more? Practise all ADO.NET MCQs · ADO.NET interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.