Docs / Custom Mapping

Custom Mapping

What this page covers#

This page explains how to take full control over result mapping by supplying your own mapper.

Why this exists#

Auto mapping is great for the common case, but sometimes you need more control.

Custom mapping exists for cases where:

  • the result shape does not line up neatly with your target type
  • you want to transform values while reading
  • you want explicit control over null handling or conversion
  • you want to optimise a hot path
  • you simply prefer to map rows yourself

The simple version#

With custom mapping, you provide a delegate that receives a SqlDataReader and returns the object for that row.

That means you can read values however you want.

Start with an inline lambda#

</> C#
var users = await db.ListAsync(
    "dbo.usp_User_List",
    static reader => new User
    {
        UserId = reader.GetInt32(reader.GetOrdinal("UserId")),
        DisplayName = reader.GetString(reader.GetOrdinal("DisplayName"))
    });

GetOrdinal("UserId") asks the reader for the position of the UserId column, and GetInt32(...) then reads that column as an int.

You can also map by ordinal position#

</> C#
var users = await db.ListAsync(
    "dbo.usp_User_List",
    static reader => new User
    {
        UserId = reader.GetInt32(0),
        DisplayName = reader.GetString(2)
    });

Both styles work because your mapper receives the real SqlDataReader.

Move to a method when the mapping grows#

</> C#
var users = await db.ListAsync(
    "dbo.usp_User_List",
    UserMapping.MapUser);
</> C#
public static class UserMapping
{
    public static User MapUser(SqlDataReader reader)
    {
        return new User
        {
            UserId = reader.GetInt32(reader.GetOrdinal("UserId")),
            DisplayName = reader.GetString(reader.GetOrdinal("DisplayName"))
        };
    }
}

Why custom mapping is useful#

Custom mapping is useful when you want full row-materialisation control without changing the rest of MooDb's calling style.

You still use SingleAsync, ListAsync, or QueryMultipleAsync, but you decide exactly how each row becomes a .NET object.

Important notes#

  • Custom mapping bypasses MooDb auto mapping.
  • Your mapper runs once per row.
  • You can map by column name or by ordinal.
  • Inline lambdas are good for short local mappings.
  • Reusable mapping methods or mapping classes are better once the mapper grows.