Introduction
Persistord is a provider-agnostic, Discord-library-agnostic persistence layer for Discord bots, built on EF Core 10.
What it is
Persistord ships the model only: entities, conventions, and module configurations. It never selects a database provider, never talks to Discord, and never references a Discord client library. You compose it into your own bot and stay in control of the provider and the gateway.
The library defines the data shape of common Discord entities and the plumbing —
a base DbContext, snowflake value conversions, and opt-in modules — while leaving
you in full control of what gets persisted, when, and with which database
provider.
The promise
The guiding promise is "persist whatever you choose to" — not state replication or gateway sync. Persistord gives you a clean, reliable relational model to write into; it never mirrors live state automatically.
Why it exists
Discord ids are 64-bit ulong snowflakes; relational providers store signed long.
Persistord handles the bit-faithful ulong ↔ long round-trip globally via a single
convention registered in ConfigureConventions, so you never annotate individual id
properties. See snowflake conversion for details.
Beyond that, the library models the core Discord graph — guilds, channels, users, members, roles, and messages — as plain POCOs, plus opt-in soft-delete and append-only history.
What it is not
The following are explicit non-goals in v1:
- No gateway event handling, no automatic sync, no reconnect backfill, no reconciliation.
- No general conflict-resolution engine.
Persistord.Coredoes ship a narrow natural-keyUpsertAsync/UpsertIfChangedAsync(create-or-update with lost-insert-race recovery) for rows the bot owns — that is deliberately not an engine, just the one write pattern the "persist what you choose" model needs. - No caching layer.
- No diff-based history (full content snapshot per change in v1).
Mapping from Discord.Net / DSharpPlus / NetCord model types to Persistord entities
is the user's responsibility — though the optional Persistord.Adapters.DiscordNet,
Persistord.Adapters.DSharpPlus and Persistord.Adapters.NetCord packages provide
ready-made mappers for all three libraries.
Packages
Persistord is split into ten NuGet packages:
Persistord — the convenience meta package. Installing it pulls in the full
library-neutral stack (Core, Messages, and History) in one reference. This is
the recommended starting point.
Persistord.Core — the foundation: snowflake conversion, the
conventions-only DiscordDbContext base class, and the abstract
DiscordGraphDbContext that adds the opt-in core skeleton entities
(GuildEntity, ChannelEntity, UserEntity, MemberEntity, RoleEntity)
for a bot that mirrors Discord's guild/channel/user/member/role graph rather
than owning its own resources.
Persistord.Messages — the optional message-persistence module. Adds
MessageEntity (with soft-delete), owned embeds, and relational attachments and
reactions, wired in via ApplyMessagesModule(). Depends on Persistord.Core.
Persistord.History — the optional append-only history module. Adds
MessageHistoryEntity with a real foreign key to MessageEntity, wired in via
ApplyHistoryModule(). Depends on Persistord.Messages.
Persistord.Adapters.DiscordNet — an optional adapter that maps Discord.Net
interface types (IGuild, IMessage, etc.) to Persistord entities via .To*Entity()
extension methods. Install only if you use Discord.Net; the core packages never
reference a Discord client library.
Persistord.Adapters.DSharpPlus — an optional adapter that maps DSharpPlus model
types (DiscordGuild, DiscordMessage, etc.) to Persistord entities via .To*Entity()
extension methods. Install only if you use DSharpPlus; the core packages never
reference a Discord client library. ToMemberEntity and ToRoleEntity take the guild
id as an argument, because DSharpPlus does not expose it on those two types.
Persistord.Adapters.NetCord — an optional adapter that maps NetCord model
types (RestGuild, RestMessage, etc.) to Persistord entities via .To*Entity()
extension methods. Install only if you use NetCord; the core packages never
reference a Discord client library.
Persistord.Managed — records of the categories, channels, anchored
messages, and webhooks a bot creates and owns, keyed by a name you chose. See
Managed Resources. Depends on Persistord.Core.
Persistord.Protection — encrypts [Protected] string columns of a
context at rest via ASP.NET Core Data Protection. See
Protection. Depends on Persistord.Core.
Persistord.Testing — in-memory SQLite fixtures and EF Core model
assertions for testing a Persistord-based context. See
Testing. Depends on Persistord.Core.
Persistord.Managed, Persistord.Protection, and Persistord.Testing are
opt-in and not part of the Persistord meta package: a bot that only
mirrors Discord never owns resources, encrypts a column, or needs the test
fixtures, so the meta package stays the library-neutral mirror stack (Core,
Messages, History) and nothing more.
The dependency graph is not linear: Messages depends on Core, History depends
on Messages, and all three of Adapters.DiscordNet, Adapters.DSharpPlus and
Adapters.NetCord depend on all three — that chain is the one the meta package
bundles. Managed, Protection, and Testing each depend on Core alone,
independently of that chain and of each other.
To get started, see Getting Started.