Seeding with C Sharp
This page shows how to seed events using the Chronicle .NET client. Seeding is sent to the Chronicle Server when the event store connects, and the server applies it once per namespace.
Define events
Section titled “Define events”Use record types for event definitions in examples:
using Cratis.Chronicle.Events;
[EventType]public record EvtSeedingUserRegistered(string Email, string DisplayName);
[EventType]public record EvtSeedingEmailVerified(string Email);
[EventType]public record EvtSeedingProfileUpdated(string DisplayName);
[EventType]public record EvtSeedingOrderPlaced(string UserId, decimal Amount);Implement a seeder
Section titled “Implement a seeder”Implement ICanSeedEvents and use IEventSeedingBuilder to define events to append:
using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingUserSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder .For<EvtSeedingUserRegistered>("user-123", [ new("john@example.com", "John") ]) .ForEventSource("user-456", [ new EvtSeedingUserRegistered("jane@example.com", "Jane"), new EvtSeedingEmailVerified("jane@example.com") ]); }}Seed multiple events of the same type
Section titled “Seed multiple events of the same type”using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingMultipleSameTypeSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("user-123", [ new("john@example.com", "John"), new("jane@example.com", "Jane") ]); }}Seed mixed event types
Section titled “Seed mixed event types”using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingMixedTypesSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.ForEventSource("user-123", [ new EvtSeedingUserRegistered("john@example.com", "John"), new EvtSeedingEmailVerified("john@example.com"), new EvtSeedingProfileUpdated("John Doe") ]); }}Development-only seeding
Section titled “Development-only seeding”If seed data should only run in development, use conditional compilation or runtime configuration:
using Cratis.Chronicle.Seeding;
#if DEBUGpublic sealed class EvtSeedingDevelopmentSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("dev-user-1", [ new("dev@example.com", "Dev User") ]); }}#endifChronicle does not distinguish between development and production seed data. Decide when to seed based on build configuration or runtime settings.
Organize seeders by feature
Section titled “Organize seeders by feature”For larger solutions, split seeders by domain or feature:
using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingUserFeatureSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("test-user-1", [ new("test1@example.com", "Test User 1") ]); }}
public sealed class EvtSeedingOrderFeatureSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingOrderPlaced>("test-order-1", [ new("test-user-1", 100.00m) ]); }}Namespace-scoped seed data
Section titled “Namespace-scoped seed data”By default, seed data applies to all namespaces in the event store. To target a specific namespace, use ForNamespace to get a scoped builder:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Seeding;
[EventType]public record EvtSeedingProductCreated(string Name, decimal Price);
[EventType]public record EvtSeedingOrganizationCreated(string Name);
[EventType]public record EvtSeedingBillingSetUp(string BillingEmail);
public sealed class EvtSeedingTenantSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { // Global seed data — applied to every namespace builder.For<EvtSeedingProductCreated>("product-1", [ new("Laptop", 1299.00m) ]);
// Namespace-scoped seed data — applied only to the "acme" namespace builder.ForNamespace("acme") .For<EvtSeedingUserRegistered>("user-1", [ new("admin@acme.com", "Acme Admin") ]);
// A second namespace with different seed data builder.ForNamespace("contoso") .For<EvtSeedingUserRegistered>("user-1", [ new("admin@contoso.com", "Contoso Admin") ]) .ForEventSource("org-1", [ new EvtSeedingOrganizationCreated("Contoso"), new EvtSeedingBillingSetUp("contoso@billing.com") ]); }}The scoped builder supports the same For<TEvent> and ForEventSource methods as the global builder. Each namespace receives only its own scoped events in addition to any global events.
How it runs
Section titled “How it runs”- Seeders are automatically discovered at application startup.
- Seed batches are sent to the Chronicle Server when the event store connects.
- The server deduplicates seeded events and applies them once per namespace.
- Events are appended in a single batch for efficient startup.
Best practices
Section titled “Best practices”- Keep seed data minimal and deterministic.
- Use clear event source IDs to make debugging easier.
- Group seeders by scenario so you can remove or adjust them easily.
- Use build flags or runtime settings to prevent seeding in production.
- Use
ForNamespacewhen seed data is tenant-specific or environment-specific to avoid polluting other namespaces.