Inline Mappers - lobodava/artisan-orm GitHub Wiki

The command and reader ReadTo* APIs provide overloads accepting a Func<SqlDataReader, T>, alongside registered [MapperFor] mappers and reflection-based auto-mapping. Check the particular overload: not every repository shortcut exposes the same arguments.

This is useful for:

  • Anonymous-typed projections — types you don't want to declare globally.
  • Tuples — when you need just two or three columns and don't want a class.
  • Existing classes you can't / don't want to decorate with [MapperFor].

Anonymous-typed projection

var summary = cmd.ReadTo(dr => new
{
    Id    = dr.GetInt32(0),
    Login = dr.GetString(1),
});

Tuple

var (id, name) = cmd.ReadTo(dr => (dr.GetInt32(0), dr.GetString(1)));

One-off class

var user = cmd.ReadTo(dr => new User
{
    Id    = dr.GetInt32  (0),
    Login = dr.GetString (1),
    Name  = dr.GetString (2),
    Email = dr.GetString (3),
});

The same pattern works for list-returning methods:

IList<(byte Id, string Code)> roles = cmd.ReadToList(dr =>
    (Id: dr.GetByte(0), Code: dr.GetString(1)));

Scalar projections and SQL NULL in 4.1.0

In the source being prepared for 4.1.0, a supplied factory controls every row actually read, including scalar and nullable outputs. SQL NULL in column zero does not bypass it. The factory chooses the source columns and handles null substitution itself:

using var cmd = repo.CreateCommand("SELECT CAST(NULL AS int) AS Ignored, 7 AS Value;");
IList<int> values = cmd.ReadToList(row => row.GetInt32(1) + 100);
// values contains 107: the factory reads column one despite NULL in column zero.

The same contract applies to the corresponding list, array, enumerable, async-stream, and multi-result factory overloads. An empty result produces no factory calls. A single-row ReadTo still returns default(T) when no row exists. Without an explicit factory, supported simple types continue to use the normal column-zero scalar path.

The factory contract is independent of the C# model's property names or order. Registered manual mappers have the same freedom to define the application's mapping. See ReadProjectionRegressionTest.cs for SQL NULL and scalar/nullable projection regressions.

When to switch to a registered [MapperFor]

If the same shape is read in more than one or two places, move it to a registered mapper — single source of truth, plus the option to also produce ObjectRow and DataTable forms (for table-valued parameters and bulk save).

Performance

Inline mappers compile to normal C# delegates. The supplied factory avoids registered-mapper discovery and expression-tree construction for that projection. Its per-row cost depends on the code inside the delegate; measure allocations and conversions when they matter.


See also:

⚠️ **GitHub.com Fallback** ⚠️