Skip to content

Mise Result Rows

SteffenBlake edited this page Sep 28, 2026 · 1 revision

Result rows

A result row describes the columns a query or returning command produces. Mark a partial class or record with your engine's [Mise] attribute, then put [Column] on each mapped property.

Define a row

// CourseRow.cs
[Mise]
public sealed partial record CourseRow
{
    [Column("course_id")]
    public required int CourseId { get; init; }

    [Column("title")]
    public required string Title { get; init; }

    [Column("capacity")]
    public required int Capacity { get; init; }
}

Here, [Mise] comes from Brigade.Net.Mise.PostgreSQL. The matching engine generates IRow<CourseRow>, BindOrdinals, and Materialize. All row-based reader and writer methods require that contract.

Select the mapped columns explicitly:

var query = new PostgreSqlQueryBuilder()
    .Select($"{CourseTbl.CourseIdCol:raw}")
    .Select($"{CourseTbl.TitleCol:raw}")
    .Select($"{CourseTbl.CapacityCol:raw}")
    .From($"{CourseTbl.Table:raw}");

Result<IReadOnlyList<CourseRow>> courses = await reader.ListAsync<CourseRow>(
    query,
    cancellationToken
);

The SQL result names, rather than their order, determine which columns populate the properties. Selecting the same set in another order still works.

Projection names

Each mapped property needs exactly one matching result column. A missing name or duplicate matching name throws InvalidMappingException. PostgreSQL matches names exactly; SQL Server, SQLite, MySQL, and MariaDB match case-insensitively.

Give joined or computed columns SQL aliases matching the row's [Column] names:

// CourseCampusRow.cs
[Mise]
public sealed partial record CourseCampusRow
{
    [Column("course_id")]
    public required int CourseId { get; init; }

    [Column("course_title")]
    public required string CourseTitle { get; init; }

    [Column("campus_name")]
    public string? CampusName { get; init; }
}
.Select($"{CourseTbl.Course.TitleCol:raw} AS course_title")
.Select($"{CampusTbl.Campus.NameCol:raw} AS campus_name")

Additional result columns do not supply missing mapped columns. Keep the projection and row type explicit so changes are easy to review.

Nullability and data types

SQL NULL maps to null for nullable properties. It throws MappingException for a non-nullable property. A left join can introduce null even when the underlying column is non-nullable; CampusName above accounts for that.

Choose property types compatible with the provider's returned values. Mapping failures are exceptions; they are not automatically converted to Result.Error.

Constructor mapping

A row needs exactly one accessible valid instance constructor. Constructor parameters match mapped properties by case-insensitive C# name and exact type. Properties not supplied through the constructor must be writable. A property-based record with an implicit empty constructor and init accessors works as above.

For a positional record, put column attributes on the generated properties:

// CampusRow.cs
[Mise]
public sealed partial record CampusRow(
    [property: Column("campus_id")] int CampusId,
    [property: Column("name")] string Name
);

Rows and their containing types must be partial. Rows cannot be static, abstract, or ref-like. Every declared and inherited instance property except indexers is mapped and needs [Column]; keep unrelated display or helper properties outside the row.

See Readers, Returned rows, and Diagnostics.

Clone this wiki locally