Custom multi-row mapping
HashSet
ConstructorInfo seed = typeof(HashSet<>).GetConstructor(Type.EmptyTypes)
?? throw new InvalidOperationException("HashSet constructor was not found.");
MethodInfo add = typeof(HashSet<>).GetMethod(nameof(HashSet<int>.Add))
?? throw new InvalidOperationException("HashSet.Add was not found.");
TypeParsingInfo.AddOrSet(typeof(HashSet<>), new MultiRowTypeParsingInfo(seed, add, null));
The added value keeps its mapping.
public record Tag(int Id, string Name) : IDbReadable;
static readonly QueryCommand GetTags = new("SELECT TagId AS Id, Name FROM tags ORDER BY TagId");
HashSet<Tag> tags = GetTags.Query<HashSet<Tag>>(cnn);
No rows still create the seed value.
static readonly QueryCommand FindTags = new("SELECT TagId AS Id, Name FROM tags WHERE TagId < 0");
HashSet<Tag> tags = FindTags.Query<HashSet<Tag>>(cnn);
// tags is empty.
The return value of HashSet<T>.Add is ignored. The HashSet<T> remains the accumulator.
Accumulator with final conversion
public sealed class Averager
{
double sum;
int count;
public void Add(double value)
{
sum += value;
count++;
}
public Average Finish() => new(count == 0 ? 0 : sum / count, count);
}
public readonly record struct Average(double Mean, int Count);
ConstructorInfo seed = typeof(Averager).GetConstructor(Type.EmptyTypes)
?? throw new InvalidOperationException("Averager constructor was not found.");
MethodInfo add = typeof(Averager).GetMethod(nameof(Averager.Add))
?? throw new InvalidOperationException("Averager.Add was not found.");
MethodInfo finish = typeof(Averager).GetMethod(nameof(Averager.Finish))
?? throw new InvalidOperationException("Averager.Finish was not found.");
TypeParsingInfo.AddOrSet(typeof(Average), new MultiRowTypeParsingInfo(seed, add, finish));
static readonly QueryCommand GetScores = new("SELECT Score FROM ratings ORDER BY RatingId");
Average average = GetScores.Query<Average>(cnn);
Compose inside another mapping
public record Artist(int Id, string Name, [Alt("Tag")] HashSet<Tag> Tags);
static readonly QueryCommand GetArtistsWithTags = new("SELECT ar.ArtistId AS Id, ar.Name, t.TagId AS TagId, t.Name AS TagName FROM artists ar JOIN artist_tags at ON at.ArtistId = ar.ArtistId JOIN tags t ON t.TagId = at.TagId ORDER BY ar.ArtistId");
List<Artist> artists = GetArtistsWithTags.Query<List<Artist>>(cnn);
HashSet<Tag> keeps the custom multi-row behavior. Tag keeps its mapping. The parent boundary still uses the grouping system.