| name | csharp-client |
| description | SpacetimeDB C#/.NET client SDK reference. Use when building C# clients that connect to SpacetimeDB (console, desktop, or any .NET app). |
| license | Apache-2.0 |
| metadata | {"author":"clockworklabs","version":"2.0","role":"client","language":"csharp","cursor_globs":"**/*.cs","cursor_always_apply":true} |
SpacetimeDB C# Client
Install: dotnet add package SpacetimeDB.ClientSDK
Connection
using SpacetimeDB;
using SpacetimeDB.Types;
var conn = DbConnection.Builder()
.WithUri("http://localhost:3000")
.WithDatabaseName("my-database")
.WithToken(savedToken)
.OnConnect((conn, identity, token) =>
{
Console.WriteLine($"Connected as: {identity}");
File.WriteAllText("auth_token.txt", token);
conn.SubscriptionBuilder()
.OnApplied(OnSubscriptionApplied)
.SubscribeToAllTables();
})
.OnConnectError(err => Console.Error.WriteLine($"Connection failed: {err}"))
.OnDisconnect((conn, err) =>
{
if (err != null) Console.Error.WriteLine($"Disconnected: {err}");
})
.Build();
Event Loop (Critical)
FrameTick() must be called in your main loop. The SDK queues all network messages and only processes them when you call FrameTick(). Without it, no callbacks fire.
while (running)
{
conn.FrameTick();
Thread.Sleep(16);
}
Thread safety: FrameTick() processes messages on the calling thread. Do NOT call it from a background thread. Do NOT access conn.Db from background threads.
Subscriptions
conn.SubscriptionBuilder()
.OnApplied(ctx => Console.WriteLine("Subscription ready"))
.SubscribeToAllTables();
conn.SubscriptionBuilder()
.OnApplied(OnSubscriptionApplied)
.AddQuery(q => q.From.Player().Where(p => p.Level.Gte(5u)))
.AddQuery(q => q.From.GameState())
.Subscribe();
conn.SubscriptionBuilder()
.OnApplied(OnSubscriptionApplied)
.Subscribe(new[] {
"SELECT * FROM player WHERE level >= 5",
"SELECT * FROM game_state"
});
Row Callbacks
conn.Db.Player.OnInsert += (EventContext ctx, Player player) =>
{
Console.WriteLine($"Player joined: {player.Name}");
};
conn.Db.Player.OnDelete += (EventContext ctx, Player player) =>
{
Console.WriteLine($"Player left: {player.Name}");
};
conn.Db.Player.OnUpdate += (EventContext ctx, Player oldPlayer, Player newPlayer) =>
{
Console.WriteLine($"Player updated: {newPlayer.Name}");
};
Reading the Client Cache
if (conn.Db.Player.Id.Find(playerId) is Player player)
{
Console.WriteLine($"Player: {player.Name}");
}
var me = conn.Db.Player.Identity.Find(myIdentity);
foreach (var p in conn.Db.Player.Level.Filter(5))
{
Console.WriteLine($"Level 5: {p.Name}");
}
foreach (var p in conn.Db.Player.Iter())
{
Console.WriteLine(p.Name);
}
int total = conn.Db.Player.Count;
Calling Reducers
conn.Reducers.CreatePlayer("Alice");
conn.Reducers.MovePlayer(10.0f, 20.0f);
conn.Reducers.SendMessage("Hello!");
Reducer Callbacks
conn.Reducers.OnSendMessage += (ReducerEventContext ctx, string text) =>
{
if (ctx.Event.Status is Status.Committed)
Console.WriteLine($"Message sent: {text}");
else if (ctx.Event.Status is Status.Failed(var reason))
Console.Error.WriteLine($"Send failed: {reason}");
};
Identity
Identity myIdentity;
if (player.Owner == myIdentity) { }
Console.WriteLine($"Identity: {identity}");