π 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 CodeData Practice: Parameters, Mapping, TransactionsQuery through parameters only, map rows to objects, and wrap multi-step writes in a transaction.
3 challenges Β· Β· ~30 min