Messages
Persistord.Messages adds MessageEntity and its related data to your context.
Apply the module in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // core skeleton + snowflake convention
modelBuilder.ApplyMessagesModule(); // messages, embeds, attachments, reactions
}
Then expose the DbSet on your derived context:
public DbSet<MessageEntity> Messages => Set<MessageEntity>();
MessageEntity shape
public class MessageEntity
{
public ulong Id { get; set; } // PK, snowflake
public ulong ChannelId { get; set; }
public ulong AuthorId { get; set; }
public string? Content { get; set; }
public DateTimeOffset? EditedAt { get; set; }
// Soft-delete
public bool IsDeleted { get; set; }
public DateTimeOffset? DeletedAt { get; set; }
public List<Embed> Embeds { get; set; } = new();
public List<AttachmentEntity> Attachments { get; set; } = new();
public List<ReactionEntity> Reactions { get; set; } = new();
}
Embeds — relational by default, JSON opt-in
Embeds are stored relationally as child rows. The Embed type is an owned
entity with two further owned types (EmbedFooter, EmbedAuthor) and a relational
EmbedField collection. Each embed and each field has a surrogate key generated by
EF Core.
This default keeps the model portable across all providers, including SQLite, where JSON document mapping for owned collections is not available by default.
If you target a provider with strong JSON support (PostgreSQL, SQL Server) and prefer
document storage, e.ToJson() is available as a power-user opt-in:
// In a custom IEntityTypeConfiguration or by overriding OnModelCreating:
modelBuilder.Entity<MessageEntity>().OwnsMany(m => m.Embeds, e =>
{
e.ToJson(); // power-user opt-in — not the default
});
ToJson() is not the default because owned-collection JSON support varies by
provider. The relational default works everywhere.
Embed model
public class Embed
{
public string? Title { get; set; }
public string? Description { get; set; }
public int? Color { get; set; }
public EmbedFooter? Footer { get; set; } // owned
public EmbedAuthor? Author { get; set; } // owned
public List<EmbedField> Fields { get; set; } = new();
}
EmbedFooter and EmbedAuthor are owned types (stored in the embed's table row).
EmbedField is a relational child with its own surrogate key.
Attachments and reactions
AttachmentEntity and ReactionEntity are relational children of MessageEntity.
AttachmentEntity.Idis the caller-supplied Discord snowflake — never store-generated. You set it from the attachment's ID in the gateway event.ReactionEntity.Idis a surrogate key generated by EF Core.
Both carry a MessageId foreign key back to the parent MessageEntity.
See also
- Soft-delete & Query Filters — how soft-delete works and how to read deleted messages.
- History — the append-only history module that builds on
MessageEntity.