跳到主要内容

查询语言与数据读取

最后更新:2026 年 7 月 16 日

PlayServ 采用类 LINQ 的语法,以便 IDE 提供语法高亮和补全提示。数据过滤是支持的,嵌套关系会按数据 Schema 自动展开。


按主键读取单个对象​

需要某个特定对象时,用按主键选择。

var player = await PlayServ.Model<Player>()
.Key(playerId)
.With(p => p.InventoryItems.Price < 500)
.With(p => p.InventoryItems.Creator.Race == "Gnome");

// sugar
var player = await PlayServ.GetPlayer(playerId);

读取集合​

读取集合用 Where(...);需要处理嵌套块时用 With(...)。

var players = await PlayServ.Model<Player>()
.Where(player => player.Age > 15)
.With(p => p.InventoryItems.Price < 500);

集合还有一种更短的语法糖写法:

var players = await PlayServ.GetPlayers(
[ p => p.Age > 15 ],
[ p => p.InventoryItems.Price < 500 ]
);
// C# 12 / .NET 8

Where 与 With:各管一段​

  • Where 限定哪些对象会被选进结果。
  • With 过滤已选对象内部的嵌套数据块。

这种分工会直接体现在服务端的查询形态上。


服务端查询转译与数据量​

查询执行时有两条规则:

  1. 查询会被转译成服务端的形态 —— 见下面的例子。
  2. 请求哪些字段,取决于代码中实际读取了哪些数据。

并非所有字段都会被自动请求。代码分析器会识别出哪些字段被读取,据此构建服务端请求 —— 其余字段保持为 NULL。

LINQ 查询位于客户端代码中分析代码分析器识别被读取的字段构建服务端查询DSL / GraphQL返回响应仅含被读取的字段未读取的字段 → NULL
注意

如果某个字段没有包含在查询的形状里,它可能一直是 NULL。这个行为与代码中读取了哪些字段直接相关。


处理多个 Model 并展开关系​

查询语言支持同时处理多个 Model,并会根据 Schema 自动展开嵌套关系。

描述 Model 之间关系的例子:

Player {
InventoryItems = InventoryItem {
Creator = Player
}
Clan = Clan {
King = Player
}
}

任意嵌套层级上的 Model 扩展​

如果某个 Model 带有专门的扩展,那么无论在哪一层嵌套中取到这个 Model,扩展都同样有效。

player.InventoryItems[3].Creator.Trade();

条件的约束:只能是 Expression​

传给 Where 或 With 的 Handler 必须是 Expression。在查询接口中请使用 Expression<Func<T,bool>>。

Where 和 With 依赖代码分析,由条件构建出 DSL 查询,并挡住不受支持的写法。


服务端的查询形态​

LINQ 风格的查询会被转译成服务端的形态。Where 条件限定对象的选择范围,With 条件过滤嵌套数据。例如这个查询:

var players = await PlayServ.Model<Player>()
.Where(player => player.Age > 15)
.Where(player => player.Clan.King.Age > 25)
.With(p => p.InventoryItems.Price < 500)
.With(p => p.InventoryItems.Creator.Race == "Gnome");

会被转译为:

Player(where: {
Age: {_gt: 15}
Clan: { King: { Age: {_gt: 25}}}
}) {
Name
Age
InventoryItems(where: {Price: {_lt: 500}}) {
Name
Creator(where: {Race: {_eq: "Gnome"}}) {
Name
Race
}
}
Clan {
Name
King {
Name
Age
}
}
}

下一步​