Curiosity

Querying the Graph

The graph query interface is a fluent C# API for traversing and querying the knowledge graph. The full interface is available inside Custom Endpoints and the Shell via the Q() / Query() global helpers. A subset is available in Data Connectors via graph.QueryAsync().

For the complete method reference, see Graph Query Language. This page covers the most common patterns and how to read node properties.


Using the Query Interface

In Endpoints and the Shell

The Q() helper creates a new query. Chain methods to build the pipeline, then either return the result directly or materialize it to C# objects for further logic.

// Return results directly from an endpoint
return Q().StartAt(N.SupportCase.Type)
          .SortByTimestamp(oldestFirst: false)
          .Take(50)
          .Emit("N");

// Iterate over results in code
foreach (var node in Q().StartAt(N.SupportCase.Type).AsEnumerable())
{
    var summary = node.GetString(N.SupportCase.Summary);
}

// Count matching nodes
int total = Q().StartAt(N.SupportCase.Type)
               .WhereString(N.SupportCase.Type, N.SupportCase.Status, "Open")
               .Count();

In Data Connectors

Use graph.QueryAsync(), which accepts a lambda over the library IQuery (a subset) and returns a QueryResults object.

var response = await graph.QueryAsync(q =>
    q.StartAt(nameof(Nodes.Device)).Take(10).Emit("N"));

var nodes = response.GetEmitted("N");

See Querying in Data Connectors for the full data connector query reference.


Materializing Results

When writing code inside an endpoint, you can choose how to materialize results depending on the use case.

Return directly from the endpoint

The Emit family of methods serializes results to the HTTP response. Use this when you want to return data from an endpoint.

Method Description
Emit(string name, params string[] fields) Return matched nodes, optionally restricting fields.
EmitWithEdges(string name, params string[] fields) Return nodes with their edge data.
EmitWithScores(string name, params string[] fields) Return nodes with their relevance scores.
EmitCount(string name) Return only the count of matched nodes.
EmitUIDs(string name) Return only node UIDs.
EmitSummary(string name) Return a structural graph summary (useful in the shell).
EmitNeighborsSummary(string name) Return a summary of edges from the current node set.

Materialize to C# objects (in-code use)

Use these when you need to work with results in code, not return them directly.

Method Return Type Description
AsEnumerable() IEnumerable<ReadOnlyNode> Lazy iteration without loading all nodes at once.
AsUIDEnumerable() IEnumerable<UID128> UIDs only — no node content loaded.
AsTypedUIDEnumerable() IEnumerable<TypedUID128> Typed UID + node type pairs.
AsScoredUIDEnumerable() IEnumerable<ScoredUID> UIDs with relevance scores.
ToList() List<ReadOnlyNode> Materialize all nodes into a list.
ToUIDList() List<UID128> All UIDs as a list.
ToScoredUIDList() List<ScoredUID> All scored UIDs as a list.
ToJsonAsync() Task<string> Serialize results to a JSON string.
Count() int Total count.
Any() bool True if any results exist.
// Lazy iteration (efficient for large sets)
foreach (var node in Q().StartAt(N.SupportCase.Type).AsEnumerable())
{
    // process each node
}

// Materialize to a list
var recentCases = Q().StartAt(N.SupportCase.Type)
                     .SortByTimestamp(oldestFirst: false)
                     .Take(20)
                     .ToList();

// Check existence
bool hasOpen = Q().StartWhereString(N.SupportCase.Type, N.SupportCase.Status, "Open").Any();

Accessing Node Properties

Nodes returned from AsEnumerable(), ToList(), or inside a Where() predicate expose typed accessor methods.

Data Type Accessor List Accessor
String GetString(key) GetStringList(key)
Integer GetInt(key) GetIntList(key)
Boolean GetBool(key) GetBoolList(key)
Float GetFloat(key) GetFloatList(key)
Double GetDouble(key) GetDoubleList(key)
Long GetLong(key) GetLongList(key)
DateTime GetTime(key) GetTimeList(key)
UID GetUID128(key) GetUID128List(key)
Dynamic node[key]

Use auto-generated constants (e.g. N.SupportCase.Content) instead of raw strings to avoid typos:

foreach (var node in Q().StartAt(N.SupportCase.Type).AsEnumerable())
{
    var id      = node.GetString(N.SupportCase.Id);
    var summary = node.GetString(N.SupportCase.Summary);
    var time    = node.GetTime(N.SupportCase.Time);
}

Common Patterns

Pagination

class PageRequest { public int Page { get; set; } }
const int pageSize = 50;
var req = Body.FromJson<PageRequest>();
return Q().StartAt(N.Part.Type)
          .Skip(req.Page * pageSize)
          .Take(pageSize)
          .Emit();
var deviceUID = Node.GetUID(N.Device.Type, "MacBook Air");
return Q().StartAt(N.SupportCase.Type)
          .IsRelatedTo(deviceUID)
          .SortByTimestamp(oldestFirst: false)
          .Take(50)
          .Emit("N");

Semantic similarity with graph filter

class Request { public string Query { get; set; } public string Manufacturer { get; set; } }
var req = Body.FromJson<Request>();
return Q().StartAtSimilarText(req.Query, nodeTypes: [N.SupportCase.Type], count: 500)
          .IsRelatedTo(Node.GetUID(N.Manufacturer.Type, req.Manufacturer))
          .EmitWithScores();

Set operations

// Union: combine two queries
var open = Q().StartWhereString(N.SupportCase.Type, N.SupportCase.Status, "Open");
var high = Q().StartWhereString(N.SupportCase.Type, N.SupportCase.Priority, "High");
return open.Union(high).Distinct().SortByTimestamp(oldestFirst: false).Emit("N");

Shell exploration

// Graph structure overview
return Q().EmitSummary();

// What edges does SupportCase have?
return Q().StartAt(N.SupportCase.Type).EmitNeighborsSummary();

// Analytics: count cases per device
return Q().StartAt(N.Device.Type).AsEnumerable()
          .ToDictionary(
              n => n.GetString(N.Device.Name),
              n => Q().StartAt(n.UID).Out(N.SupportCase.Type).Count()
          );

Next Steps

© 2026 Curiosity. All rights reserved.