Skip to content

Repository files navigation

Persistord

A provider-agnostic, Discord-library-agnostic persistence layer for Discord bots, built on EF Core 10. Ships the model only — entities, conventions, and module configurations. You stay in control of the database provider and the Discord gateway.

CI CD Docs

.NET NuGet Version License: MIT codecov Mutation Score

Getting Started · Documentation · Samples

Why

Discord ids are 64-bit ulong snowflakes; relational providers store signed long. Persistord handles the bit-faithful ulong ↔ long round-trip globally, models the core Discord graph (guilds, channels, users, members, roles, messages), and adds opt-in soft-delete and append-only history — without coupling you to a specific database or Discord library.

Packages

Package Downloads Adds Depends on
Persistord Downloads meta package — bundles Core, Messages, and History Core, Messages, History
Persistord.Core Downloads snowflake conversion, base DiscordDbContext, core skeleton entities
Persistord.Messages Downloads MessageEntity (soft-delete), embeds, attachments, reactions Core
Persistord.History Downloads append-only MessageHistoryEntity with a real FK to messages Messages
Persistord.Adapters.DiscordNet Downloads .To*Entity() mappers from Discord.Net types Core, Messages, History

The core packages are independent of any Discord client library. Install the DiscordNet adapter only if you use Discord.Net.

Install

# Recommended: the full library-neutral stack in one package
dotnet add package Persistord

# Or install modules individually:
dotnet add package Persistord.Core
dotnet add package Persistord.Messages      # optional: message persistence
dotnet add package Persistord.History       # optional: requires Messages
dotnet add package Persistord.Adapters.DiscordNet   # optional: Discord.Net mappers

Quick start

1. Derive a context

Inherit DiscordDbContext, expose the module DbSets you want, and apply the module configurations in OnModelCreating:

using Microsoft.EntityFrameworkCore;
using Persistord.Core;
using Persistord.History;
using Persistord.History.Entities;
using Persistord.Messages;
using Persistord.Messages.Entities;

public sealed class MyBotContext : DiscordDbContext
{
    public MyBotContext(DbContextOptions<MyBotContext> options) : base(options) { }

    public DbSet<MessageEntity> Messages => Set<MessageEntity>();
    public DbSet<MessageHistoryEntity> MessageHistory => Set<MessageHistoryEntity>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);   // core skeleton + snowflake convention
        modelBuilder.ApplyMessagesModule();   // omit if you don't persist messages
        modelBuilder.ApplyHistoryModule();    // requires ApplyMessagesModule()
    }
}

Core entities (Guilds, Channels, Users, Members, Roles) are already exposed by the base context — you only declare the module DbSets.

2. Choose a provider

The consumer owns the provider choice. Any EF Core 10 relational provider works (PostgreSQL, SQL Server, SQLite, …); the snowflake conversion is applied automatically.

services.AddDbContextFactory<MyBotContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("Bot")));

3. Use short-lived contexts

A bot is long-lived and concurrent; a DbContext is neither thread-safe nor meant to live forever. Create one per unit of work via the factory and dispose it:

await using var db = await factory.CreateDbContextAsync();

db.Messages.Add(new MessageEntity
{
    Id = message.Id,
    ChannelId = message.ChannelId,
    AuthorId = message.Author.Id,
    Content = message.Content,
});

await db.SaveChangesAsync();

If you use Discord.Net, the adapter maps gateway/REST types for you:

using Persistord.Adapters.DiscordNet;
using Persistord.History.Entities;

db.Messages.Add(socketMessage.ToMessageEntity());   // embeds, attachments, reactions included
db.MessageHistory.Add(socketMessage.ToHistoryEntity(HistoryChangeType.Created));
await db.SaveChangesAsync();

Soft-delete & history

Deleting a message sets IsDeleted / DeletedAt rather than removing the row, and a default query filter hides soft-deleted messages (IgnoreQueryFilters() to include them, or ApplyMessagesModule(filterDeleted: false) to disable). Because the row survives, MessageHistoryEntity's foreign key to it — including the row logging the deletion — stays valid.

Documentation

Full guides and the API reference live on the documentation site (built with DocFX). Start with Getting Started, browse the Guides and Recipes, or check Troubleshooting if something isn't working.

Building

dotnet restore
dotnet build
dotnet test

Requires the .NET 10 SDK. Formatting is enforced with ReSharper (dotnet jb cleanupcode Persistord.slnx --profile="ReformatAndReorder").

License

MIT © Clergue Valentin

About

A provider-agnostic, Discord-library-agnostic persistence layer for Discord bots, built on EF Core 10. Ships the model only — entities, conventions, and module configurations. You stay in control of the database provider and the Discord gateway.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages