csharp-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SpacetimeDB C# SDK Reference

SpacetimeDB C# SDK 参考

Build Configuration (.NET 10)

构建配置(.NET 10)

New .NET 10 modules build as managed libraries by default. Use the CLI for WASM builds, or select the runtime identifier explicitly when invoking dotnet:
sh
spacetime init my-project --lang csharp --dotnet-version 10
新的 .NET 10 模块默认构建为托管库。对于 WASM 构建,请使用 CLI,或在调用 dotnet 时明确选择运行时标识符:
sh
spacetime init my-project --lang csharp --dotnet-version 10

From the generated module directory:

From the generated module directory:

dotnet build # managed DLL dotnet publish -c Release -r wasi-wasm # NativeAOT-LLVM WASM spacetime build # CLI selects WASM automatically

- Do not set an unconditional `RuntimeIdentifier=wasi-wasm` in .NET 10 projects.
  Keep WASM-only settings such as `PublishTrimmed`, `SelfContained`, and
  `MSBuildEnableWorkloadResolver=false` conditional on that RID.
- The module must reference both `Microsoft.DotNet.ILCompiler.LLVM` and
  `runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM`
  (version `10.0.0-*`) conditionally on `RuntimeIdentifier=wasi-wasm`.
  The runtime package no longer supplies these dependencies transitively.
  Keep the dotnet-experimental NuGet feed configured for WASM restores.
- `spacetime build` and `spacetime publish` select WASM automatically. .NET 10
  does not require `--native-aot` or an AOT environment variable.
- Allow restore when switching between managed and WASM builds; do not reuse
  `--no-restore` across modes. For multi-targeted projects, select `-f net10.0`
  when publishing and restrict the LLVM 10 package references to .NET 10.
- .NET 8 behavior is unchanged: WASM/JIT by default, with `--native-aot` selecting
  NativeAOT where supported. The current flag is `SPACETIMEDB_WASM_AOT`; the CLI
  also sets/clears `EXPERIMENTAL_WASM_AOT` for older projects. Do not manually
  define the C# compiler symbol to choose a .NET 10 build mode.
- Module dependencies use ordinary `ProjectReference` or packaged managed
  assemblies. .NET 10 composition uses generated assembly descriptors, not the
  dependency's standalone WASM exports; no special root/library build flag is needed.
dotnet build # managed DLL dotnet publish -c Release -r wasi-wasm # NativeAOT-LLVM WASM spacetime build # CLI selects WASM automatically

- 不要在 .NET 10 项目中无条件设置 `RuntimeIdentifier=wasi-wasm`。
  应使 `PublishTrimmed`、`SelfContained` 和
  `MSBuildEnableWorkloadResolver=false` 等仅适用于 WASM 的设置以该 RID 为条件。
- 模块必须以 `RuntimeIdentifier=wasi-wasm` 为条件,同时引用
  `Microsoft.DotNet.ILCompiler.LLVM` 和
  `runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM`
  (版本 `10.0.0-*`)。运行时包不再通过传递方式提供这些依赖。
  为 WASM 还原操作保留 dotnet-experimental NuGet 源的配置。
- `spacetime build` 和 `spacetime publish` 会自动选择 WASM。.NET 10
  不需要 `--native-aot` 或 AOT 环境变量。
- 在托管构建和 WASM 构建之间切换时,允许执行还原操作;不要跨模式复用
  `--no-restore`。对于多目标项目,发布时选择 `-f net10.0`,并将 LLVM 10
  包引用限制为 .NET 10。
- .NET 8 行为保持不变:默认使用 WASM/JIT;在支持的情况下使用 `--native-aot`
  选择 NativeAOT。当前标志为 `SPACETIMEDB_WASM_AOT`;CLI 也会为旧项目设置/清除
  `EXPERIMENTAL_WASM_AOT`。不要手动定义 C# 编译器符号来选择 .NET 10 构建模式。
- 模块依赖项使用普通 `ProjectReference` 或打包的托管程序集。.NET 10
  组合使用生成的程序集描述符,而不是依赖项独立导出的 WASM;不需要特殊的根项目/库构建标志。

Module Structure

模块结构

Reducers are static methods in a
static partial class
; tables are
public partial struct
s. This reference keeps everything in one
public static partial class Module
, which needs only
using SpacetimeDB;
:
Methods exported through SpacetimeDB attributes, including reducers, procedures, views, HTTP handlers, and routers, must be
public static
; generated bindings invoke them from another class.
csharp
using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Table(Accessor = "ScoreRecord", Public = true)]
    public partial struct ScoreRecord
    {
        [PrimaryKey]
        [AutoInc]
        public ulong Id;
        public Identity Owner;
        public uint Value;
    }

    [SpacetimeDB.Reducer]
    public static void AddRecord(ReducerContext ctx, uint value)
    {
        ctx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = value });
    }
}
Reducer 是
static partial class
中的静态方法;表是
public partial struct
。本参考将所有内容放在同一个
public static partial class Module
中,因此只需要
using SpacetimeDB;
:
通过 SpacetimeDB 特性导出的方法(包括 reducer、procedure、view、HTTP handler 和 router)必须是
public static
;生成的绑定会从另一个类中调用它们。
csharp
using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Table(Accessor = "ScoreRecord", Public = true)]
    public partial struct ScoreRecord
    {
        [PrimaryKey]
        [AutoInc]
        public ulong Id;
        public Identity Owner;
        public uint Value;
    }

    [SpacetimeDB.Reducer]
    public static void AddRecord(ReducerContext ctx, uint value)
    {
        ctx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = value });
    }
}

Tables

表

[SpacetimeDB.Table(...)]
on a
public partial struct
.
Accessor
should be PascalCase:
csharp
[SpacetimeDB.Table(Accessor = "Entity", Public = true)]
public partial struct Entity
{
    [PrimaryKey]
    [AutoInc]
    public ulong Id;
    [SpacetimeDB.Index.BTree]
    public Identity Owner;
    public string Name;
    [SpacetimeDB.Index.BTree]
    public bool Active;
}
Options:
Accessor = "PascalCase"
(recommended),
Public = true
,
Scheduled = nameof(ReducerFn)
,
ScheduledAt = nameof(field)
,
Event = true
ctx.Db
accessors use the
Accessor
name:
ctx.Db.Entity
,
ctx.Db.ScoreRecord
.
[SpacetimeDB.Table(...)]
应用于
public partial struct
。
Accessor
应使用 PascalCase:
csharp
[SpacetimeDB.Table(Accessor = "Entity", Public = true)]
public partial struct Entity
{
    [PrimaryKey]
    [AutoInc]
    public ulong Id;
    [SpacetimeDB.Index.BTree]
    public Identity Owner;
    public string Name;
    [SpacetimeDB.Index.BTree]
    public bool Active;
}
选项:
Accessor = "PascalCase"
(推荐)、
Public = true
、
Scheduled = nameof(ReducerFn)
、
ScheduledAt = nameof(field)
、
Event = true
ctx.Db
访问器使用
Accessor
名称:
ctx.Db.Entity
、
ctx.Db.ScoreRecord
。

Column Types

列类型

C# typeNotes
byte
/
ushort
/
uint
/
ulong
unsigned integers
U128
/
U256
large unsigned integers (SpacetimeDB types)
sbyte
/
short
/
int
/
long
signed integers
I128
/
I256
large signed integers (SpacetimeDB types)
float
/
double
floats
bool
boolean
string
text
List<T>
list/array
Identity
user identity
ConnectionId
connection handle
Timestamp
server timestamp (microseconds since epoch)
TimeDuration
duration in microseconds
Uuid
UUID
Optional columns: nullable types (
string? Nickname
,
uint? HighScore
)
C# 类型说明
byte
/
ushort
/
uint
/
ulong
无符号整数
U128
/
U256
大型无符号整数(SpacetimeDB 类型)
sbyte
/
short
/
int
/
long
有符号整数
I128
/
I256
大型有符号整数(SpacetimeDB 类型)
float
/
double
浮点数
bool
布尔值
string
文本
List<T>
列表/数组
Identity
用户身份
ConnectionId
连接句柄
Timestamp
服务端时间戳(自 Unix 纪元起的微秒数)
TimeDuration
以微秒为单位的时长
Uuid
UUID
可选列:可空类型(
string? Nickname
、
uint? HighScore
)

Column Attributes

列特性

The complete set of column attributes:
csharp
[PrimaryKey]          // primary key
[AutoInc]             // auto-increment (use 0 as placeholder on insert)
[Unique]              // unique constraint; indexes the column, enables .Find()
[SpacetimeDB.Index.BTree]  // btree index (enables .Filter() on this column)
[Default(true)]       // migration-safe default for a newly appended field
Defaults support compatible addition of a newly appended field. Do not apply
[Default(...)]
to primary-key, unique, or auto-increment fields.
列特性的完整集合:
csharp
[PrimaryKey]          // primary key
[AutoInc]             // auto-increment (use 0 as placeholder on insert)
[Unique]              // unique constraint; indexes the column, enables .Find()
[SpacetimeDB.Index.BTree]  // btree index (enables .Filter() on this column)
[Default(true)]       // migration-safe default for a newly appended field
默认值支持以兼容方式添加新追加的字段。不要将
[Default(...)]
应用于主键、唯一或自增字段。

Indexes

索引

Write the index attribute fully qualified:
[SpacetimeDB.Index.BTree]
. Prefer inline for single-column; multi-column uses struct-level:
csharp
// Inline (preferred for single-column):
[SpacetimeDB.Index.BTree]
public ulong AuthorId;
// Access: ctx.Db.Post.AuthorId.Filter(authorId)

// Multi-column (struct-level):
[SpacetimeDB.Table(Accessor = "Membership")]
[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = new[] { nameof(GroupId), nameof(UserId) })]
public partial struct Membership { public ulong GroupId; public Identity UserId; ... }
Prefer a multi-column index over filtering by one column and looping.
完整限定地编写索引特性:
[SpacetimeDB.Index.BTree]
。单列优先使用内联方式;多列使用结构体级别:
csharp
// Inline (preferred for single-column):
[SpacetimeDB.Index.BTree]
public ulong AuthorId;
// Access: ctx.Db.Post.AuthorId.Filter(authorId)

// Multi-column (struct-level):
[SpacetimeDB.Table(Accessor = "Membership")]
[SpacetimeDB.Index.BTree(Accessor = "ByGroupUser", Columns = new[] { nameof(GroupId), nameof(UserId) })]
public partial struct Membership { public ulong GroupId; public Identity UserId; ... }
相比按单列过滤后循环,优先使用多列索引。

Reducers

Reducer

csharp
[SpacetimeDB.Reducer]
public static void CreateEntity(ReducerContext ctx, string name, int age)
{
    ctx.Db.Entity.Insert(new Entity { Owner = ctx.Sender, Name = name, Age = age, Active = true });
}

// No arguments:
[SpacetimeDB.Reducer]
public static void DoReset(ReducerContext ctx) { ... }
csharp
[SpacetimeDB.Reducer]
public static void CreateEntity(ReducerContext ctx, string name, int age)
{
    ctx.Db.Entity.Insert(new Entity { Owner = ctx.Sender, Name = name, Age = age, Active = true });
}

// No arguments:
[SpacetimeDB.Reducer]
public static void DoReset(ReducerContext ctx) { ... }

DB Operations

数据库操作

csharp
var row = ctx.Db.Entity.Insert(new Entity { Name = "Sample" });   // Insert; returns the row with AutoInc fields assigned
ctx.Db.Entity.Id.Find(entityId);                                  // Find by PK → Entity? (nullable)
ctx.Db.Entity.Identity.Find(ctx.Sender);                          // Find by unique column → Entity?
if (ctx.Db.Entity.Id.Find(entityId) is { } entity) { ... }        // unwrap Entity? before member access
ctx.Db.Item.AuthorId.Filter(authorId);                            // Filter by index → IEnumerable<Item>
ctx.Db.Entity.Iter();                                             // All rows → IEnumerable<Entity>
ctx.Db.Entity.Count;                                              // Count rows
ctx.Db.Entity.Id.Update(existing with { Name = newName });        // Update by PK
ctx.Db.Entity.Id.Delete(entityId);                                // Delete by PK
Note: Filter/Iter return enumerables. Use
.ToList()
if you need to sort or mutate.
The pattern is
ctx.Db.{Accessor}.{ColumnName}.{Method}(value)
for all indexed column operations.
csharp
var row = ctx.Db.Entity.Insert(new Entity { Name = "Sample" });   // Insert; returns the row with AutoInc fields assigned
ctx.Db.Entity.Id.Find(entityId);                                  // Find by PK → Entity? (nullable)
ctx.Db.Entity.Identity.Find(ctx.Sender);                          // Find by unique column → Entity?
if (ctx.Db.Entity.Id.Find(entityId) is { } entity) { ... }        // unwrap Entity? before member access
ctx.Db.Item.AuthorId.Filter(authorId);                            // Filter by index → IEnumerable<Item>
ctx.Db.Entity.Iter();                                             // All rows → IEnumerable<Entity>
ctx.Db.Entity.Count;                                              // Count rows
ctx.Db.Entity.Id.Update(existing with { Name = newName });        // Update by PK
ctx.Db.Entity.Id.Delete(entityId);                                // Delete by PK
注意:Filter/Iter 返回可枚举对象。如果需要排序或修改,请使用
.ToList()
。
所有索引列操作都遵循
ctx.Db.{Accessor}.{ColumnName}.{Method}(value)
模式。

Lifecycle Hooks

生命周期钩子

csharp
[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void OnInit(ReducerContext ctx) { ... }

[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx) { ... }

[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx) { ... }
ctx.ConnectionId
is
ConnectionId?
, including in connection lifecycle reducers. Check or unwrap it before storing it in a non-nullable column or passing it to an index accessor.
csharp
[SpacetimeDB.Reducer(ReducerKind.Init)]
public static void OnInit(ReducerContext ctx) { ... }

[SpacetimeDB.Reducer(ReducerKind.ClientConnected)]
public static void OnConnect(ReducerContext ctx) { ... }

[SpacetimeDB.Reducer(ReducerKind.ClientDisconnected)]
public static void OnDisconnect(ReducerContext ctx) { ... }
ctx.ConnectionId
的类型是
ConnectionId?
,即使在连接生命周期 reducer 中也是如此。在将其存入不可为空列或传递给索引访问器之前,请先检查或解包。

Views

视图

csharp
// Anonymous view (same result for all clients):
[SpacetimeDB.View(Accessor = "ActiveUsers", Public = true)]
public static List<Entity> ActiveUsers(AnonymousViewContext ctx)
{
    return ctx.Db.Entity.Active.Filter(true).ToList();
}

// Per-user view:
[SpacetimeDB.View(Accessor = "MyEntities", Public = true)]
public static List<Entity> MyEntities(ViewContext ctx)
{
    return ctx.Db.Entity.Owner.Filter(ctx.Sender).ToList();
}
View contexts expose read-only table handles. These support
Count
plus
Find
/
Filter
on declared indexes, but not full-table
Iter()
. Start procedural view traversal from an indexed lookup or filter.
Query-builder views use
ViewContext
,
ctx.From
, and return
IQuery<T>
directly. Use
Where
for predicates and
RightSemijoin
when the result should contain right-side rows that have a matching left-side row:
csharp
ctx.From.Article().Where(article => article.Published.Eq(true));
ctx.From.Subscription().RightSemijoin(
    ctx.From.Account(),
    (subscription, account) => subscription.AccountId.Eq(account.Id)
);
Declare a procedural view primary key in its attribute:
[SpacetimeDB.View(Accessor = "CatalogEntry", Public = true, PrimaryKey = nameof(CatalogRow.Sku))]
.
Procedural-view primary keys are explicit schema metadata. Add one only when the view itself is required to expose a primary key; a source table's primary key is not inherited by the view.
Inclusive btree ranges use tuples, for example
ctx.Db.Shipment.DeliverBy.Filter((new Timestamp(1_000), new Timestamp(2_000)))
.
Views can also accept arguments (2.13+), after the context:
csharp
[SpacetimeDB.View(Accessor = "EntityById", Public = true)]
public static Entity? EntityById(AnonymousViewContext ctx, ulong id) =>
    ctx.Db.Entity.Id.Find(id);
Floating-point arguments and primary keys are rejected on argument-bearing views. See client skills for SQL subscription syntax and restrictions.
csharp
// Anonymous view (same result for all clients):
[SpacetimeDB.View(Accessor = "ActiveUsers", Public = true)]
public static List<Entity> ActiveUsers(AnonymousViewContext ctx)
{
    return ctx.Db.Entity.Active.Filter(true).ToList();
}

// Per-user view:
[SpacetimeDB.View(Accessor = "MyEntities", Public = true)]
public static List<Entity> MyEntities(ViewContext ctx)
{
    return ctx.Db.Entity.Owner.Filter(ctx.Sender).ToList();
}
视图上下文提供只读表句柄。这些句柄支持
Count
,以及对已声明索引使用
Find
/
Filter
,但不支持全表
Iter()
。过程式视图的遍历应从索引查找或过滤开始。
查询构建器视图使用
ViewContext
、
ctx.From
,并直接返回
IQuery<T>
。使用
Where
表达谓词;当结果应包含存在匹配左侧行的右侧行时,使用
RightSemijoin
:
csharp
ctx.From.Article().Where(article => article.Published.Eq(true));
ctx.From.Subscription().RightSemijoin(
    ctx.From.Account(),
    (subscription, account) => subscription.AccountId.Eq(account.Id)
);
在过程式视图的特性中声明主键:
[SpacetimeDB.View(Accessor = "CatalogEntry", Public = true, PrimaryKey = nameof(CatalogRow.Sku))]
。
过程式视图的主键是显式的 schema 元数据。仅当视图本身需要公开主键时才添加;源表的主键不会被视图继承。
包含边界范围的 btree 范围使用元组,例如
ctx.Db.Shipment.DeliverBy.Filter((new Timestamp(1_000), new Timestamp(2_000)))
。
视图也可以在上下文之后接受参数(2.13+):
csharp
[SpacetimeDB.View(Accessor = "EntityById", Public = true)]
public static Entity? EntityById(AnonymousViewContext ctx, ulong id) =>
    ctx.Db.Entity.Id.Find(id);
带参数的视图不接受浮点参数和主键。有关 SQL 订阅语法和限制,请参阅 client skill。

Client Visibility Filters

客户端可见性过滤器

csharp
[ClientVisibilityFilter]
public static readonly Filter PrivateNoteFilter = new Filter.Sql(
    "SELECT * FROM OwnedRow WHERE Owner = :sender"
);
csharp
[ClientVisibilityFilter]
public static readonly Filter PrivateNoteFilter = new Filter.Sql(
    "SELECT * FROM OwnedRow WHERE Owner = :sender"
);

Reducer Context API

Reducer 上下文 API

ReducerContext
(
ctx
) is the only source of sender identity, time, and randomness; stdlib clocks and RNG are unavailable in modules.
csharp
// Auth: ctx.Sender is the caller's Identity
if (row.Owner != ctx.Sender)
    throw new Exception("unauthorized");

// Server timestamp (deterministic per reducer call)
ctx.Db.Item.Insert(new Item { CreatedAt = ctx.Timestamp, .. });

// Timestamp arithmetic
var expiry = ctx.Timestamp + new TimeDuration(delayMicros);

// Deterministic RNG
int roll = ctx.Rng.Next(1, 7);          // [1, 7): inclusive 1, exclusive 7
double f = ctx.Rng.NextDouble();        // [0.0, 1.0)

// Timestamp → milliseconds since epoch
timestamp.MicrosecondsSinceUnixEpoch / 1000
ReducerContext
(
ctx
)是发送者身份、时间和随机数的唯一来源;模块中无法使用标准库时钟和 RNG。
csharp
// Auth: ctx.Sender is the caller's Identity
if (row.Owner != ctx.Sender)
    throw new Exception("unauthorized");

// Server timestamp (deterministic per reducer call)
ctx.Db.Item.Insert(new Item { CreatedAt = ctx.Timestamp, .. });

// Timestamp arithmetic
var expiry = ctx.Timestamp + new TimeDuration(delayMicros);

// Deterministic RNG
int roll = ctx.Rng.Next(1, 7);          // [1, 7): inclusive 1, exclusive 7
double f = ctx.Rng.NextDouble();        // [0.0, 1.0)

// Timestamp → milliseconds since epoch
timestamp.MicrosecondsSinceUnixEpoch / 1000

Submodules

子模块

C# assembly composition requires SpacetimeDB/Runtime 2.13+, .NET 10, and C# 14 in the root and dependencies. NativeAOT-LLVM module builds require Windows or Linux, not macOS. Reference module assemblies via
ProjectReference
or NuGet. If libraries live beneath the root project, exclude their source from the root compile glob (
<Compile Remove="libraries/**/*.cs" />
). Dependencies, including transitive ones, register once in
public
unless some module mounts them with a
Namespace
attribute:
csharp
// Root Lib.cs, before namespace/type declarations:
[assembly: SpacetimeDB.Namespace(typeof(AuthLib.Marker), Accessor = "MyAuth", Name = "auth_data")]
The marker is any accessible type in the dependency assembly. Root code uses
ctx.Db.MyAuth.User
and
ctx.From.MyAuth.User()
; library code keeps
ctx.Db.User
and
ctx.From.User()
. To call a library helper, pass
ctx.As.MyAuth
(
tx.As.MyAuth
inside
WithTx
; 2.14+). Passing
ctx
directly works only while the library has one unambiguous instance; otherwise it throws
InvalidOperationException
. Helpers share the caller's transaction; namespaces are not helper security boundaries.
Accessor
controls C# members;
Name
pins the canonical namespace used in SQL (
auth_data.<table>
). Without
Name
, the root's case policy derives it (
MyAuth
→
my_auth
by default). Changing the canonical namespace drops its tables and their data; keep
Name
fixed to rename only accessors. Named dependencies retain their naming policy;
public
dependencies inherit the root's and cannot explicitly conflict with it.
In 2.14+, any module can mount its own dependencies, and the same assembly can be mounted more than once with independent tables at each path; 2.13 allows only root mounts, once per assembly. Cross-language composition is unsupported. Named dependencies cannot declare lifecycle reducers, nonempty environments, or RLS filters. Define RLS in the root with canonical qualified table names. Named-submodule routes are ignored; root handlers may delegate to library helpers. Dependencies in
public
can contribute lifecycle reducers (subject to uniqueness), environment declarations, and routes.
C# 程序集组合要求根项目和依赖项均使用 SpacetimeDB/Runtime 2.13+、.NET 10 和 C# 14。NativeAOT-LLVM 模块构建需要 Windows 或 Linux,不能使用 macOS。通过
ProjectReference
或 NuGet 引用模块程序集。如果库位于根项目下,请从根项目的编译 glob 中排除其源文件(
<Compile Remove="libraries/**/*.cs" />
)。除非某个模块使用
Namespace
特性挂载依赖项,否则包括传递依赖在内的所有依赖项都注册到
public
中:
csharp
// Root Lib.cs, before namespace/type declarations:
[assembly: SpacetimeDB.Namespace(typeof(AuthLib.Marker), Accessor = "MyAuth", Name = "auth_data")]
Marker
可以是依赖程序集中的任意可访问类型。根代码使用
ctx.Db.MyAuth.User
和
ctx.From.MyAuth.User()
;库代码继续使用
ctx.Db.User
和
ctx.From.User()
。要调用库辅助函数,请传入
ctx.As.MyAuth
(在
WithTx
内使用
tx.As.MyAuth
;2.14+)。仅当库中只有一个唯一且无歧义的实例时,才可以直接传入
ctx
;否则将抛出
InvalidOperationException
。辅助函数与调用方共享事务;namespace 不是辅助函数的安全边界。
Accessor
控制 C# 成员名;
Name
固定 SQL 中使用的规范 namespace(
auth_data.<table>
)。未设置
Name
时,将按根项目的命名大小写策略派生(默认
MyAuth
→
my_auth
)。更改规范 namespace 会删除其表及数据;若只想重命名访问器,请保持
Name
不变。命名依赖项保留其命名策略;
public
依赖项继承根项目的策略,并且不能与其显式冲突。
在 2.14+ 中,任何模块都可以挂载自己的依赖项,并且同一程序集可以挂载多次,每个路径都有独立的表;2.13 仅允许根项目挂载,每个程序集一次。不支持跨语言组合。命名依赖项不能声明生命周期 reducer、非空环境或 RLS 过滤器。在根项目中使用规范限定表名定义 RLS。命名子模块的路由会被忽略;根 handler 可以委派给库辅助函数。位于
public
中的依赖项可以贡献生命周期 reducer(须满足唯一性要求)、环境声明和路由。

Environment Variables

环境变量

csharp
[SpacetimeDB.Env]
public partial struct EnvironmentSchema
{
    public string API_KEY;
    [SpacetimeDB.EnvValues("development", "production")]
    public string MODE;
    public string? LOG_LEVEL;
}
// In a root entry point:
// ctx.Env.API_KEY: string; ctx.Env.LOG_LEVEL: string?
// ctx.Env.Get("LOG_LEVEL"): string?
Enable nullable annotations for optional
string?
fields. One
[SpacetimeDB.Env]
struct declares the environment;
[EnvValues(...)]
restricts allowed strings. Colliding accessor names remain available through
Get("KEY")
.
See the concepts skill for shared environment and secret-handling rules.
csharp
[SpacetimeDB.Env]
public partial struct EnvironmentSchema
{
    public string API_KEY;
    [SpacetimeDB.EnvValues("development", "production")]
    public string MODE;
    public string? LOG_LEVEL;
}
// In a root entry point:
// ctx.Env.API_KEY: string; ctx.Env.LOG_LEVEL: string?
// ctx.Env.Get("LOG_LEVEL"): string?
为可选的
string?
字段启用可空注解。一个
[SpacetimeDB.Env]
结构体声明环境;
[EnvValues(...)]
限制允许的字符串。即使访问器名称发生冲突,仍可通过
Get("KEY")
访问。
有关共享环境和密钥处理规则,请参阅 concepts skill。

Scheduled Tables

定时表

Declare the scheduled table and its reducer in the same
Module
class so
nameof(...)
resolves:
csharp
[SpacetimeDB.Table(
    Accessor = "TickTimer",
    Scheduled = nameof(Tick),
    ScheduledAt = nameof(ScheduledAt),
    Public = true
)]
public partial struct TickTimer
{
    [PrimaryKey]
    [AutoInc]
    public ulong ScheduledId;
    public ScheduleAt ScheduledAt;
}

[SpacetimeDB.Reducer]
public static void Tick(ReducerContext ctx, TickTimer timer)
{
    // timer row is auto-deleted after this reducer runs
}

// One-time: fires once at a specific time
var at = new ScheduleAt.Time(ctx.Timestamp + new TimeDuration(10_000_000));
// Repeating: fires on an interval
var at = new ScheduleAt.Interval(TimeSpan.FromSeconds(5));

ctx.Db.TickTimer.Insert(new TickTimer { ScheduledId = 0, ScheduledAt = at });
Scheduled reducer callbacks use the ordinary
[SpacetimeDB.Reducer]
attribute. There is no
ReducerKind.Scheduled
; the table's
Scheduled
option associates the callback.
To construct a
ConnectionId
from a 128-bit numeric representation, encode it as exactly 16 little-endian bytes and call
ConnectionId.From(bytes)
. The result is nullable and must be checked before use.
在同一个
Module
类中声明定时表及其 reducer,以便
nameof(...)
正确解析:
csharp
[SpacetimeDB.Table(
    Accessor = "TickTimer",
    Scheduled = nameof(Tick),
    ScheduledAt = nameof(ScheduledAt),
    Public = true
)]
public partial struct TickTimer
{
    [PrimaryKey]
    [AutoInc]
    public ulong ScheduledId;
    public ScheduleAt ScheduledAt;
}

[SpacetimeDB.Reducer]
public static void Tick(ReducerContext ctx, TickTimer timer)
{
    // timer row is auto-deleted after this reducer runs
}

// One-time: fires once at a specific time
var at = new ScheduleAt.Time(ctx.Timestamp + new TimeDuration(10_000_000));
// Repeating: fires on an interval
var at = new ScheduleAt.Interval(TimeSpan.FromSeconds(5));

ctx.Db.TickTimer.Insert(new TickTimer { ScheduledId = 0, ScheduledAt = at });
定时 reducer 回调使用普通的
[SpacetimeDB.Reducer]
特性。不存在
ReducerKind.Scheduled
;通过表的
Scheduled
选项关联回调。
要从 128 位数值表示构造
ConnectionId
,请将其编码为恰好 16 个小端序字节,并调用
ConnectionId.From(bytes)
。结果可为空,使用前必须检查。

Procedures and HTTP

Procedure 与 HTTP

Procedures are unstable APIs, so modules using them should include
#pragma warning disable STDB_UNSTABLE
. They receive
ProcedureContext
and may return
[SpacetimeDB.Type]
values:
csharp
[SpacetimeDB.Type]
public partial struct ResultValue { public string Value; }

[SpacetimeDB.Procedure]
public static ResultValue Inspect(ProcedureContext ctx, string input) =>
    new() { Value = input };
Outbound HTTP is available through
ctx.Http
; handle its success/error result before using the response. Open short database transactions with
ctx.WithTx
. Perform network I/O before opening the transaction, and keep only database work inside its callback.
csharp
var result = ctx.Http.Get(uri);
var text = result.Match(
    response => response.Body.ToStringUtf8Lossy(),
    error => throw new Exception(error.Message)
);

ctx.WithTx(tx =>
{
    tx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = 1 });
    return 0;
});
WithTx
is generic: its callback must return a value (use
return 0;
when no result is needed). In procedures and handlers, access tables only via
tx.Db
, never
ctx.Db
.
For non-GET requests, construct and send an
HttpRequest
directly:
csharp
var result = ctx.Http.Send(new HttpRequest
{
    Uri = uri,
    Method = SpacetimeDB.HttpMethod.Post,
    Headers = new() { new HttpHeader("content-type", "text/plain") },
    Body = HttpBody.FromString(payload),
});
HttpResponse.StatusCode
is
ushort
. HTTP headers are
HttpHeader
values, not tuples, and each header's
Value
is
byte[]
; decode text values with
System.Text.Encoding.UTF8.GetString(header.Value)
. Treat bodies as bytes: use
HttpBody.FromString(...)
to create text,
new HttpBody(bytes)
to supply raw bytes,
ToBytes()
to read raw bytes, and
ToStringUtf8Lossy()
to read text. There is no
HttpBody.FromBytes
, and
ToString()
does not return body contents. Qualify
SpacetimeDB.HttpMethod
when .NET's implicit
System.Net.Http
imports could make the name ambiguous.
Scheduled procedures use the ordinary scheduled-table shape. Its
Scheduled
name refers to a
[SpacetimeDB.Procedure]
method taking
ProcedureContext
plus the scheduled row, and database access inside that procedure goes through
ctx.WithTx
.
Inbound HTTP uses handler attributes and one router. Handler database access also goes through
ctx.WithTx
:
csharp
[SpacetimeDB.HttpHandler]
public static HttpResponse Health(HandlerContext ctx, HttpRequest request) => new(
    200, HttpVersion.Http11, new(), HttpBody.FromString("ok")
);

[SpacetimeDB.HttpRouter]
public static Router Routes() => SpacetimeDB.Router.New().Get("/health", Handlers.Health);
Handlers
is generated from methods marked
[SpacetimeDB.HttpHandler]
; do not declare it yourself. Reference an attributed method in a router as
Handlers.MethodName
.
Procedure 是不稳定 API,因此使用它们的模块应包含
#pragma warning disable STDB_UNSTABLE
。它们接收
ProcedureContext
,并且可以返回
[SpacetimeDB.Type]
值:
csharp
[SpacetimeDB.Type]
public partial struct ResultValue { public string Value; }

[SpacetimeDB.Procedure]
public static ResultValue Inspect(ProcedureContext ctx, string input) =>
    new() { Value = input };
可通过
ctx.Http
发起出站 HTTP 请求;使用响应前,请处理其成功/错误结果。使用
ctx.WithTx
开启短数据库事务。请在开启事务前执行网络 I/O,事务回调内仅保留数据库工作。
csharp
var result = ctx.Http.Get(uri);
var text = result.Match(
    response => response.Body.ToStringUtf8Lossy(),
    error => throw new Exception(error.Message)
);

ctx.WithTx(tx =>
{
    tx.Db.ScoreRecord.Insert(new ScoreRecord { Id = 0, Owner = ctx.Sender, Value = 1 });
    return 0;
});
WithTx
是泛型的:其回调必须返回一个值(不需要结果时使用
return 0;
)。在 procedure 和 handler 中,只能通过
tx.Db
访问表,绝不能使用
ctx.Db
。
对于非 GET 请求,直接构造并发送
HttpRequest
:
csharp
var result = ctx.Http.Send(new HttpRequest
{
    Uri = uri,
    Method = SpacetimeDB.HttpMethod.Post,
    Headers = new() { new HttpHeader("content-type", "text/plain") },
    Body = HttpBody.FromString(payload),
});
HttpResponse.StatusCode
的类型是
ushort
。HTTP 标头是
HttpHeader
值,而不是元组,并且每个标头的
Value
都是
byte[]
;使用
System.Text.Encoding.UTF8.GetString(header.Value)
解码文本值。应对正文进行字节级处理:使用
HttpBody.FromString(...)
创建文本,使用
new HttpBody(bytes)
提供原始字节,使用
ToBytes()
读取原始字节,使用
ToStringUtf8Lossy()
读取文本。不存在
HttpBody.FromBytes
,且
ToString()
不会返回正文内容。如果 .NET 隐式引入的
System.Net.Http
可能导致名称产生歧义,请显式限定
SpacetimeDB.HttpMethod
。
定时 procedure 使用普通的定时表结构。其
Scheduled
名称指向一个
[SpacetimeDB.Procedure]
方法,该方法接收
ProcedureContext
和定时行,procedure 内的数据库访问通过
ctx.WithTx
完成。
入站 HTTP 使用 handler 特性和一个 router。handler 的数据库访问同样通过
ctx.WithTx
完成:
csharp
[SpacetimeDB.HttpHandler]
public static HttpResponse Health(HandlerContext ctx, HttpRequest request) => new(
    200, HttpVersion.Http11, new(), HttpBody.FromString("ok")
);

[SpacetimeDB.HttpRouter]
public static Router Routes() => SpacetimeDB.Router.New().Get("/health", Handlers.Health);
Handlers
由标记了
[SpacetimeDB.HttpHandler]
的方法生成;不要自行声明它。在 router 中通过
Handlers.MethodName
引用带特性的方法。

Custom Types

自定义类型

csharp
[SpacetimeDB.Type]
public enum Status { Online, Away, Offline }

[SpacetimeDB.Type]
public partial struct Point { public float X; public float Y; }
Tagged enums (discriminated unions): a
partial record
with empty body and no constructor parameters. Payloads are
[Type] partial struct
s:
csharp
[SpacetimeDB.Type]
public partial struct Circle { public int Radius; }

[SpacetimeDB.Type]
public partial struct Rectangle { public int Width; public int Height; }

[SpacetimeDB.Type]
public partial record Shape : SpacetimeDB.TaggedEnum<(Circle Circle, Rectangle Rectangle)> { }

// Construct variants via the generated nested constructors:
var a = new Shape.Circle(new Circle { Radius = 10 });
var b = new Shape.Rectangle(new Rectangle { Width = 4, Height = 6 });
csharp
[SpacetimeDB.Type]
public enum Status { Online, Away, Offline }

[SpacetimeDB.Type]
public partial struct Point { public float X; public float Y; }
带标签的枚举(可辨识联合):使用空主体且没有构造函数参数的
partial record
。变体载荷是
[Type] partial struct
:
csharp
[SpacetimeDB.Type]
public partial struct Circle { public int Radius; }

[SpacetimeDB.Type]
public partial struct Rectangle { public int Width; public int Height; }

[SpacetimeDB.Type]
public partial record Shape : SpacetimeDB.TaggedEnum<(Circle Circle, Rectangle Rectangle)> { }

// Construct variants via the generated nested constructors:
var a = new Shape.Circle(new Circle { Radius = 10 });
var b = new Shape.Rectangle(new Rectangle { Width = 4, Height = 6 });