Snowflake Conversion
Discord IDs are 64-bit ulong snowflakes. Most relational providers — including
PostgreSQL/Npgsql and SQL Server — lack native unsigned 64-bit support and store
integers as signed long. Persistord bridges the gap in one place so you never
have to annotate individual properties.
How it works
Persistord.Core ships two EF Core value converters:
UlongToLongConverter— convertsulong↔longusingunchecked((long)value)andunchecked((ulong)value).NullableUlongToLongConverter— the same round-trip forulong?↔long?.
The unchecked cast is bit-faithful: the 64 bits are reinterpreted without range
checking, so every value — including snowflakes with the high bit set — survives
storage exactly.
Global registration
DiscordDbContext registers both converters in ConfigureConventions, so the
conversion applies to every ulong and ulong? property in your derived context
automatically:
protected override void ConfigureConventions(ModelConfigurationBuilder builder)
{
builder.Properties<ulong>().HaveConversion<UlongToLongConverter>();
builder.Properties<ulong?>().HaveConversion<NullableUlongToLongConverter>();
}
You never annotate individual IDs. Inherit DiscordDbContext and all snowflake
properties in your model — including those in module entities — are handled.
Keys
The conversion above covers values: it makes a ulong storable at all. It says
nothing about whether EF should generate that value or expect the caller to supply
it. A separate SnowflakeKeyConvention, also registered by DiscordDbContext,
handles that half: it marks every ulong or ulong? property that is part of a
primary key ValueGeneratedNever(), so EF never treats a snowflake key as a
store-generated identity column. This runs for the skeleton entities and for the
consumer's own entities alike — inherit DiscordDbContext (or
DiscordGraphDbContext) and any ulong key you add gets the same treatment
without a fluent call. Explicit configuration still wins: the convention writes at
convention precedence, so a property you've configured yourself (with
ValueGeneratedOnAdd(), for example) is left as you configured it.
This is a breaking change from 1.0.0-beta2, where a bare ulong primary
key was store-generated by EF's own default convention. See
Upgrading from 1.0.0-beta2 for what
changes in your next migration and how to opt back into a store-generated key
if you genuinely want one.
Storage note
Snowflakes are stored as long in the database column. Discord snowflakes remain
below long.MaxValue until roughly the year 2084, so signed storage is safe in
practice. The converter is nevertheless bit-faithful and would round-trip correctly
even past that point. Both the conversion and the key convention operate on "all
unsigned 64-bit", not specifically "Discord snowflakes" — a Steam64 id or any other
ulong property in your model goes through them the same way, and that's intended.
See also
- Core Graph — the conventions-only
DiscordDbContext, the opt-inDiscordGraphDbContext, and the skeleton entities whoseulongIDs this conversion covers.