Type registration
Positional constructor mapping
public readonly record struct PositionalValue<T>(T Value);
TypeParsingInfo.AddOrSet(typeof(PositionalValue<>), CtorTypeInfo.Instance);
static readonly QueryCommand CountAlbums = new("SELECT COUNT(*) FROM albums");
PositionalValue<int> value = CountAlbums.Query<PositionalValue<int>>(cnn);
The registration participates wherever PositionalValue<T> is mapped.
Custom scalar mapping
public readonly record struct LocalDate(DateTime Value);
sealed class LocalDateTypeParsingInfo : ScalarTypeParsingInfo<LocalDate>
{
static readonly MethodInfo ConvertMethod = typeof(LocalDateTypeParsingInfo)
.GetMethod(nameof(Convert), BindingFlags.Static | BindingFlags.NonPublic)
?? throw new InvalidOperationException("Convert was not found.");
protected override DbItemPlan? TryCreatePlan(Type targetType, Type parentType, ParamInfo slot, ColumnInfo column, int ordinal)
{
if (column.Type != typeof(DateTime))
return null;
ITypeConverter converter = new MethodCallConverter(ConvertMethod);
if (Nullable.GetUnderlyingType(targetType) is not null)
converter = new NullableWrapperConverter(converter);
return new ConvertedScalarPlan(parentType, converter, slot.NameComparer.GetDefaultName(), slot.NullColHandler, ordinal);
}
static LocalDate Convert(DateTime value) => new(value);
}
TypeParsingInfo.AddOrSet(typeof(LocalDate), new LocalDateTypeParsingInfo());
The same registration participates at the root or inside another mapped type.
public record Event(int Id, LocalDate Date);
static readonly QueryCommand GetEvent = new("SELECT EventId AS Id, EventDate AS Date FROM events WHERE EventId = @eventId");
Event item = GetEvent.Query<Event>(cnn, new { eventId = 12 });
Open and closed generic registrations
public readonly record struct Result<T>(T Value);
TypeParsingInfo open = TypeParsingInfo.GetOrAdd(typeof(Result<>));
TypeParsingInfo integers = TypeParsingInfo.GetOrAdd<Result<int>>(saveAsGenericDefinitionWhenGeneric: false);
The exact closed registration is checked before the open generic registration.
Result<int> exact Result<int>
Result<string> open Result<>
Registration initializers
Initializers affect registrations created through the registration creation paths after the initializer is installed.
ParamInfo.RegistrationInitializer = static slot =>
{
if (string.Equals(slot.NameComparer.GetDefaultName(), "Id", StringComparison.OrdinalIgnoreCase))
slot.SetAbortOnNull(true);
return slot;
};
A directly constructed ParamInfo does not run that initializer.
MethodCtorInfo.RegistrationInitializer = static path =>
{
if (path.TargetType == typeof(Album))
path.Flags |= MethodCtorInfo.AdditionalFlags.CanCompleteWithMembers;
return path;
};
A directly constructed MethodCtorInfo does not run that initializer. Paths created through MethodCtorInfo.TryNew do.
TypeParsingInfo.RegistrationInitializer = static (type, generated) =>
{
if (type == typeof(Playlist) && generated is ICanUpdateGroupKey grouping)
grouping.GroupKey = new EqualityGroupingRule("Id");
return generated;
};
A generated registration passes through this initializer before publication. A registration supplied directly to AddOrSet does not.
Replace and remove registrations
TypeParsingInfo.AddOrSet(typeof(LocalDate), new LocalDateTypeParsingInfo());
bool removed = TypeParsingInfo.TryRemove(typeof(LocalDate), out TypeParsingInfo? previous);
Parsers already created from an older registration keep their existing behavior until those parsers are invalidated.