Zero-Config Orleans Persistence — Auto-Provisioning SQL Server Tables

Eric Brasher March 26, 2026 at 11:00 AM 5 min read

One of the most common friction points when deploying Orleans-based systems to production is the SQL Server setup. Orleans requires specific tables for clustering, grain persistence, reminders, and streaming — and missing any of them means the silo fails to start. FabrCore's OrleansSqlServerInitializer eliminates this entirely by auto-provisioning every required table at startup.

The Manual Setup Problem

Orleans ships SQL scripts for each provider subsystem — clustering, persistence, reminders, and streaming. In a typical deployment, an operator would need to run these scripts against the database before the silo could start. Miss one table, and the silo throws an opaque error about missing schema objects. This was especially painful for developers running locally for the first time, or teams spinning up new environments.

The scripts themselves are well-documented in the Orleans repository, but the workflow of "create database, run four scripts, then start your app" adds unnecessary steps to what should be a single-command experience.

FabrCore takes the position that if you provide a connection string and set ClusteringMode to SqlServer, the framework should handle the rest.

How OrleansSqlServerInitializer Works

When AddFabrCoreServer detects SqlServer clustering mode, it registers the OrleansSqlServerInitializer as a hosted service that runs before the Orleans silo starts. The initializer carries embedded SQL scripts for all four subsystems and executes them idempotently against the configured database.

All Orleans tables are created under a dedicated orlns schema, keeping them cleanly separated from application tables. The four table groups are:

SubsystemSchemaPurpose
ClusteringorlnsSilo membership, liveness, and cluster coordination
PersistenceorlnsGrain state storage for agents, clients, and management data
RemindersorlnsPersistent timers that survive grain deactivation and silo restarts
StreamingorlnsPub/sub state for agent-to-agent and agent-to-client messaging

The scripts are embedded as resources in the FabrCore.Host assembly, so there is nothing to download, copy, or reference externally. Each script uses IF NOT EXISTS guards, meaning the initializer is safe to run on every startup — it creates what is missing and skips what is already there.

Configuration

The only configuration required is what you already provide for Orleans itself. Set the clustering mode to SqlServer and provide a connection string:

JSON — appsettings.json Orleans configuration
{
  "Orleans": {
    "ClusterId": "prod",
    "ServiceId": "fabrcore",
    "ClusteringMode": "SqlServer",
    "ConnectionString": "Server=localhost;Database=FabrCore;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}

That is the complete configuration. On the first startup, FabrCore creates the orlns schema if it does not exist, then runs each embedded script to provision the clustering, persistence, reminders, and streaming tables. On subsequent startups, the idempotency guards skip all CREATE statements.

If you need separate databases for clustering and storage, use the optional StorageConnectionString:

JSON — Separate storage connection
{
  "Orleans": {
    "ClusterId": "prod",
    "ServiceId": "fabrcore",
    "ClusteringMode": "SqlServer",
    "ConnectionString": "Server=localhost;Database=FabrCore;Trusted_Connection=True;TrustServerCertificate=True;",
    "StorageConnectionString": "Server=localhost;Database=FabrCoreStorage;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}

When StorageConnectionString is provided, the initializer runs the persistence and streaming scripts against that connection, while clustering and reminders use the primary ConnectionString. When omitted, all tables go to the same database.

The server setup code remains minimal:

C# — Program.cs with SqlServer clustering
using FabrCore.Host;

var builder = WebApplication.CreateBuilder(args);

builder.AddFabrCoreServer(new FabrCoreServerOptions
{
    AdditionalAssemblies = [typeof(MyAgent).Assembly]
});

var app = builder.Build();
app.UseFabrCoreServer();
app.Run();

No SQL scripts to run. No schema to create manually. Point at a database and start the application.

What Gets Provisioned

Behind the scenes, FabrCore registers Orleans providers with specific named constants. Understanding these names is useful if you ever need to inspect the tables directly or configure the advanced path with UseOrleans:

ConstantValuePurpose
FabrCoreOrleansConstants.StorageProviderName"fabrcoreStorage"Grain state for agents, clients, and management
FabrCoreOrleansConstants.PubSubStoreName"PubSubStore"Orleans streaming pub/sub state
FabrCoreOrleansConstants.StreamProviderName"fabrcoreStreams"Agent-to-agent and agent-to-client messaging
Reminder service(any)Agent timers and persistent reminders

If you use the advanced path — calling AddFabrCoreServices and UseOrleans separately for full control over providers — you are responsible for ensuring these named providers are registered. The auto-provisioning only applies to the simple AddFabrCoreServer path.

C# — Advanced path with required named providers
builder.AddFabrCoreServices(new FabrCoreServerOptions
{
    AdditionalAssemblies = [typeof(MyAgent).Assembly]
});

builder.UseOrleans(siloBuilder =>
{
    // Storage — must register these named providers
    siloBuilder.AddAdoNetGrainStorage(
        FabrCoreOrleansConstants.StorageProviderName, o =>
            o.ConnectionString = connStr);
    siloBuilder.AddAdoNetGrainStorage(
        FabrCoreOrleansConstants.PubSubStoreName, o =>
            o.ConnectionString = connStr);

    // Streams — must use this name
    siloBuilder.AddMemoryStreams(
        FabrCoreOrleansConstants.StreamProviderName);

    // Register FabrCore grains
    siloBuilder.AddFabrCore([typeof(MyAgent).Assembly]);
});

Built with FabrCore on .NET 10.


Eric Brasher

Builder of FabrCore and OpenCaddis.