Skip to main content
πŸ“œ WAYPOINT LESSON

Mapping Rows and the Repository Pattern

⭐⭐ intermediate⏳ 16 min readπŸ“ Lesson 133 of 265

Turning row dictionaries into domain objects, and hiding data access behind an interface.

Row mapping: the boring glue

Readers hand you rows; the domain wants objects. The mapping function is where nulls become defaults, column names are spelled once, and types are converted:

private static User Map(System.Collections.Generic.Dictionary<string, object?> row)
    => new User(
        Name: (string)row["name"]!,
        Balance: Convert.ToDecimal(row["balance"] ?? 0m));

Keep mapping in ONE place β€” a mapper method or the repository itself β€” so a schema change touches one file.

The repository: a collection-shaped faΓ§ade

A repository exposes the domain's idea of persistence β€” Add, Get, Find β€” while hiding SQL inside:

public interface IUserRepository
{
    void Add(User user);
    User? FindByName(string name);
    System.Collections.Generic.IReadOnlyList<User> All();
}

public sealed class SqlUserRepository : IUserRepository
{
    private readonly CjDb _db;
    public SqlUserRepository(CjDb db) => _db = db;   // injected (Module 11)
    // ... every SQL string lives here, parameterized, nowhere else ...
}

Benefits: business code reads like the domain; a different backend is an implementation swap; and testing needs only a fake repository (no database at all).

What belongs where

  • SQL strings: repository implementations only.
  • Domain logic: services β€” never inside repositories.
  • Mapping: between the two, in the repository.
  • A repository that returns IQueryable invites leaks; keep the boundary concrete (lists, single objects).

Check your understanding

  • Where does a new WHERE clause go? (The one repository implementation that owns that table's SQL.)
  • Why does the service take IUserRepository, not SqlUserRepository? (Depend on the abstraction; swap implementations; test with fakes.)

⚑ Now practice

Ready to Code
Data Practice: Parameters, Mapping, TransactionsQuery through parameters only, map rows to objects, and wrap multi-step writes in a transaction.
3 challenges Β· Β· ~30 min