Skip to content

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.

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 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")
]);
}
}
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")
]);
}
}
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")
]);
}
}

If seed data should only run in development, use conditional compilation or runtime configuration:

using Cratis.Chronicle.Seeding;
#if DEBUG
public sealed class EvtSeedingDevelopmentSeeding : ICanSeedEvents
{
public void Seed(IEventSeedingBuilder builder)
{
builder.For<EvtSeedingUserRegistered>("dev-user-1", [
new("dev@example.com", "Dev User")
]);
}
}
#endif

Chronicle does not distinguish between development and production seed data. Decide when to seed based on build configuration or runtime settings.

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)
]);
}
}

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.

Register corrected seed definitions without reconnecting

Section titled “Register corrected seed definitions without reconnecting”

When registration fails, Chronicle retains the attempted entries in the event store’s default Seeding buffer. That makes retrying an unchanged idempotent batch safe after a transient failure. If the definitions themselves were wrong, create an independent empty buffer so the corrected set is not combined with the retained set:

using Cratis.Chronicle;
using Cratis.Chronicle.Seeding;
public static class EvtSeedingCorrection
{
public static async Task Register(IEventStore eventStore)
{
var correctedSeeding = eventStore.CreateEventSeeding();
correctedSeeding.For<EvtSeedingUserRegistered>("user-123", [
new("john@example.com", "John Doe")
]);
await correctedSeeding.Register();
}
}

The new buffer uses the event store’s existing connection, event types, serializer, and client configuration. A failure from its Register() call still propagates normally.

  • Seeders are automatically discovered at application startup.
  • Seed batches are sent to the Chronicle Server when the event store connects.
  • Every entry is applied once per namespace, and seeding is idempotent across restarts: entries that have already been appended are skipped on the next run.
  • An entry that your seeder yields twice is two events, not one. Two events of the same type, on the same event source, carrying the same payload are two facts, and both are appended.
  • Events are appended in batches of 100. A batch is validated as a whole before anything is written, so a single entry that violates a constraint means none of its batch is appended. The server reports the rejection at Error with the violations, leaves those entries unseeded, and fails registration. The remote client surfaces that failure through its normal transport error. Retry the unchanged buffer for a transient failure; after correcting the definitions, restart the process or use CreateEventSeeding() to offer a fresh set.
  • 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 ForNamespace when seed data is tenant-specific or environment-specific to avoid polluting other namespaces.