Grouping
Inferred parent values
public record Album(int Id, string Title) : IDbReadable;
public record Artist(int Id, string Name, List<Album> Albums);
Id Name AlbumsId AlbumsTitle
1 AC/DC 10 High Voltage
1 AC/DC 11 Let There Be Rock
2 Queen 20 Jazz
Values mapped before the first nested multi-row value form the default parent boundary. The first two rows remain one Artist. The third row starts another.
Rows for one group stay consecutive.
Explicit key
public record Invoice(List<int> LineIds, [GroupKey] int InvoiceId);
LineIds InvoiceId
15 100
16 100
20 101
Several key values form one compound key.
public record OrderLine([GroupKey] int OrderId, [GroupKey] int ProductId, List<string> Serials);
Ordinary key values use EqualityComparer<T>.Default.
Boundary column without a member
[GroupKeyColumns("CustomerId")]
public record CustomerInvoices(string CustomerName, List<int> InvoiceIds);
List<CustomerInvoices> customers = cnn.Query<List<CustomerInvoices>>("SELECT CustomerId, CustomerName, InvoiceId AS InvoiceIds FROM invoices ORDER BY CustomerId");
CustomerId participates in the boundary without becoming a result member.
Boundary method
public record ShipmentBatch([Alt("ShippedAt")] DateTime Start, List<string> Items) : IDbReadable
{
[GroupKey]
public static (bool Same, DateTime Next) WithinSevenDays(DateTime saved, DateTime shippedAt)
{
bool same = (shippedAt - saved).TotalDays <= 7;
return (same, same ? saved : shippedAt);
}
}
The method receives saved boundary state and the current row value. It returns whether the row stays in the group and the next saved state.
A construction can carry its own method rule.
public sealed class ShipmentBatch : IDbReadable
{
[GroupKeyMethod(nameof(WithinSameDay))]
public ShipmentBatch(DateTime start, List<string> items) { }
public static (bool Same, DateTime Next) WithinSameDay(DateTime saved, DateTime current)
=> (saved.Date == current.Date, current);
}
Setup registration
TypeParsingInfoHelper.SetGroupKey<Playlist>(nameof(Playlist.Id));
TypeParsingInfoHelper.SetGroupKey<CustomerSummary>(nameof(CustomerSummary.Id), nameof(CustomerSummary.Country));
TypeParsingInfoHelper.SetGroupKeyColumns<ImportRow>("AccountId", "Currency");
TypeParsingInfoHelper.SetGroupKeyMethod<MonthlyReport>(nameof(MonthlyReport.ByMonth));
TypeParsingInfoHelper.ClearGroupKey<Playlist>();
Rule order
[GroupKeyColumns("Region")]
public sealed class Sale : IDbReadable
{
public Sale([GroupKey] DateTime date, List<decimal> amounts) { }
public Sale(string region, List<decimal> amounts) { }
}
A rule on the selected construction is tried before the type rule. The type rule is tried before inferred grouping. A custom rule can return no boundary so negotiation continues to the next source.
An application-defined boundary can implement IGroupingRule.
Missing boundary
public record Report(List<int> Rows, int Total);
Report report = cnn.Query<Report>("SELECT RowValue AS Rows, Total FROM report_rows ORDER BY RowNumber");
// RINKU3002 when no usable parent boundary can be negotiated.