SqlDataReader
SQL Server
C#
database programming
.NET development

Check for column name in a SqlDataReader object

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Accessing a missing column in SqlDataReader throws runtime exceptions, which is common when queries evolve or optional projections are introduced. Defensive column checks make mapper code more resilient, especially in services that support multiple query versions. The key is to check existence efficiently and avoid repeated schema scans in hot loops.

Why Missing Columns Happen

SqlDataReader is fast and forward-only, but it assumes you know the exact column set. In real systems, result shape can vary because of:

  • optional joins
  • feature-flagged SELECT lists
  • stored procedure branch logic
  • backward compatibility across API versions

Unsafe access:

csharp
var value = reader["OptionalColumn"]; // throws if missing

Safer mappers check schema first, then read values conditionally.

Extension Method: HasColumn

A common helper loops through available field names.

csharp
1using System;
2using System.Data.SqlClient;
3
4public static class SqlDataReaderExtensions
5{
6    public static bool HasColumn(this SqlDataReader reader, string columnName)
7    {
8        for (int i = 0; i < reader.FieldCount; i++)
9        {
10            if (string.Equals(reader.GetName(i), columnName, StringComparison.OrdinalIgnoreCase))
11            {
12                return true;
13            }
14        }
15
16        return false;
17    }
18}

Usage:

csharp
1if (reader.HasColumn("Department"))
2{
3    int ordinal = reader.GetOrdinal("Department");
4    if (!reader.IsDBNull(ordinal))
5    {
6        string department = reader.GetString(ordinal);
7        Console.WriteLine(department);
8    }
9}

Always call HasColumn before GetOrdinal on uncertain columns.

Cache Ordinals for Performance

If mapping many rows, repeatedly scanning columns per field is wasteful. Build an ordinal map once.

csharp
1using System;
2using System.Collections.Generic;
3using System.Data.SqlClient;
4
5public static Dictionary<string, int> BuildOrdinalMap(SqlDataReader reader)
6{
7    var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
8
9    for (int i = 0; i < reader.FieldCount; i++)
10    {
11        map[reader.GetName(i)] = i;
12    }
13
14    return map;
15}

Then read safely:

csharp
1var ordinals = BuildOrdinalMap(reader);
2
3if (ordinals.TryGetValue("Department", out int idx) && !reader.IsDBNull(idx))
4{
5    string department = reader.GetString(idx);
6}

This pattern is efficient for high-row-count readers.

Defensive DTO Mapping Example

A robust mapper can handle optional columns while still enforcing required ones.

csharp
1public sealed class UserRow
2{
3    public int Id { get; set; }
4    public string Name { get; set; } = "";
5    public string? Department { get; set; }
6}
7
8public static UserRow MapUser(SqlDataReader reader, Dictionary<string, int> ordinals)
9{
10    var user = new UserRow
11    {
12        Id = reader.GetInt32(ordinals["Id"]),
13        Name = reader.GetString(ordinals["Name"])
14    };
15
16    if (ordinals.TryGetValue("Department", out int depIdx) && !reader.IsDBNull(depIdx))
17    {
18        user.Department = reader.GetString(depIdx);
19    }
20
21    return user;
22}

Required columns should still fail fast if missing, while optional columns are guarded.

GetSchemaTable Alternative

If you need richer metadata such as data types and nullability, use schema table.

csharp
1using System;
2using System.Data;
3using System.Data.SqlClient;
4
5public static bool HasColumnViaSchema(SqlDataReader reader, string columnName)
6{
7    DataTable schema = reader.GetSchemaTable();
8    if (schema == null)
9    {
10        return false;
11    }
12
13    foreach (DataRow row in schema.Rows)
14    {
15        var name = row["ColumnName"]?.ToString();
16        if (string.Equals(name, columnName, StringComparison.OrdinalIgnoreCase))
17        {
18            return true;
19        }
20    }
21
22    return false;
23}

This is more expensive, so prefer it when metadata beyond name checks is needed.

Query Contract Best Practices

Column checks are useful, but stable query contracts reduce defensive complexity.

Practical recommendations:

  • keep explicit column aliases in SQL
  • avoid SELECT * in production data-access code
  • version stored procedure outputs when schema evolves
  • log missing optional columns for observability

Consistent projections make mapper behavior easier to reason about.

Common Pitfalls

A common pitfall is calling GetOrdinal("Column") before checking existence, which throws and bypasses safety logic.

Another issue is performing case-sensitive comparisons for column names when query aliases vary by environment.

Teams also run schema loops per row and per field, creating avoidable overhead in large result sets.

Using defensive checks for all columns can hide real contract violations. Keep required fields strict and optional fields guarded.

Finally, swallowing mapping errors without query context makes debugging schema drift difficult.

Summary

  • 'SqlDataReader will throw when you access missing columns by name.'
  • Use HasColumn or cached ordinals for safe optional-field access.
  • Cache column metadata once for better performance.
  • Keep required columns strict and optional columns defensive.
  • Prefer stable SQL projection contracts to reduce runtime surprises.

Course illustration
Course illustration

All Rights Reserved.