Curiosity

Querying in Data Connectors

Data connectors can query the graph to look up existing nodes during ingestion — for example, to read properties before linking or to check whether something exists. Queries use graph.QueryAsync(), which accepts a lambda over the Curiosity.Library.IQuery interface and returns a QueryResults object.

The library IQuery is a focused subset of the full backend IQuery available in endpoints. It works over HTTP: the query is built, serialized, and sent to the workspace, and results come back as QueryResults. Methods like AsEnumerable() and ToList() do not exist in this context — use Emit() to specify what to return, then read from QueryResults.

Important

Query capabilities in Data Connectors are limited to read operations. For complex business logic, graph traversals, or write operations beyond ingestion, use Custom Endpoints instead.

Issuing a Query

// Count all device nodes
var r = await graph.QueryAsync(q => q.StartAt(nameof(Nodes.Device)).EmitCount("C"));
var count = r.GetEmittedCount("C");

// Fetch the first 10 devices, returning only their Name field
var r2 = await graph.QueryAsync(q =>
    q.StartAt(nameof(Nodes.Device))
     .Take(10)
     .Emit("N", [nameof(Nodes.Device.Name)]));

var nodes = r2.GetEmitted("N")
              .ToDictionary(n => n.UID, n => n.GetField<string>(nameof(Nodes.Device.Name)));

The QueryResults object exposes:

Method Description
GetEmitted(key) Returns IReadOnlyList<EmittedNode> for the given emit key.
GetEmittedCount(key) Returns the int count emitted under the given key.

EmittedNode provides GetField<T>(string field) for reading typed property values.

Available IQuery Methods

The library IQuery supports the following operations. For the full backend IQuery reference (endpoints/shell), see Graph Query Language.

Starting Points

Method Description
StartAt(string nodeType) All nodes of the given type.
StartAt(Node node) Start from a specific node — use Node.FromKey(type, key).
StartAt(Node[] nodes) Start from a set of nodes.
StartAt(string nodeType, string[] keys) All nodes of the given type identified by the given keys.
StartWhereString(string nodeType, string field, string value) Nodes where a string field matches.
StartWhereString(string nodeType, string field, string[] values) Nodes where a string field matches any of a set.

Traversal

Method Description
Out() Traverse all outgoing edges.
Out(string nodeType) Traverse to a specific node type.
Out(string nodeType, string edgeType) Traverse via a specific edge type.
Out(string nodeType, string[] edgeTypes) Traverse via multiple edge types.
Out(string[] nodeTypes, string edgeType) Traverse to multiple node types via one edge type.
Out(string[] nodeTypes, string[] edgeTypes) Traverse to multiple types via multiple edge types.
OutMany(int levels, string[] nodeTypes, string[] edgeTypes, bool distinct) Multi-hop traversal.

Filtering

Method Description
OfType(string nodeType) Keep only nodes of the given type.
OfTypes(string[] nodeTypes) Keep only nodes of any of the given types.
ExceptType(string nodeType) Remove nodes of the given type.
ExceptTypes(string[] nodeTypes) Remove nodes of any of the given types.
IsRelatedTo(Node node) Keep nodes related to a specific node.
IsRelatedTo(Node[] nodes) Keep nodes related to any of the given nodes.
IsRelatedTo(string nodeType) Keep nodes related to any node of the given type.
IsRelatedTo(string[] nodeTypes) Keep nodes related to any of the given types.
IsRelatedToVia(Node node, string edgeType) Keep nodes related via a specific edge type.
IsRelatedToVia(string nodeType, string edgeType) Keep nodes related via matching node/edge type.
IsRelatedToVia(string[] nodeTypes, string[] edgeTypes) Keep nodes related via any matching combination.
IsNotRelatedTo(...) Remove nodes related to the given node(s)/type(s).
IsNotRelatedToVia(...) Remove nodes related via the given edge type.
WhereString(string nodeType, string field, string value) Keep nodes where a string field matches.
WhereString(string nodeType, string field, string[] values) Keep nodes where a string field matches any of a set.
WhereNotString(string nodeType, string field, string value) Keep nodes where a string field does not match.
WhereTimestamp(DateTimeOffset from, DateTimeOffset to, bool insideBoundary) Filter by timestamp range.

Sorting and Pagination

Method Description
SortByTimestamp(bool oldestFirst) Sort by creation time.
SortByLastUpdated(bool newestFirst) Sort by modification time.
SortByConnectivity(bool mostConnectedFirst) Sort by edge count.
Skip(int count) Skip the first N results.
Take(int count) Limit to N results.

Output

Method Description
Emit(string key) Return all matched nodes under a key.
Emit(string key, string[] fields) Return nodes with only specific fields.
EmitCount(string key) Return only the count under a key.
EmitWithEdges(string key) Return nodes with their edge data.
EmitWithEdges(string key, string[] fields) Return nodes with edges, restricted to specific fields.

Common Patterns

Linking to a node by key without a round-trip

The most efficient pattern during ingestion is Node.FromKey(), which avoids a query entirely:

// Link part to device without fetching the device node first
graph.Link(partNode, Node.FromKey(nameof(Nodes.Device), deviceName), Edges.PartOf, Edges.HasPart);

The edge is stored by type and key and becomes fully active once both nodes exist.

Looking up a node to read its properties

var r = await graph.QueryAsync(q =>
    q.StartAt(Node.FromKey(nameof(Nodes.Manufacturer), "Apple"))
     .Emit("N"));

var manufacturerNode = r.GetEmitted("N").FirstOrDefault();
if (manufacturerNode is not null)
{
    var name = manufacturerNode.GetField<string>(nameof(Nodes.Manufacturer.Name));
}

Traversal during ingestion

var r = await graph.QueryAsync(q =>
    q.StartAt(Node.FromKey(nameof(Nodes.Manufacturer), "Apple"))
     .Out(nameof(Nodes.Device))
     .Emit("D"));

foreach (var device in r.GetEmitted("D"))
{
    var deviceName = device.GetField<string>(nameof(Nodes.Device.Name));
}

Filtering by relationship

var r = await graph.QueryAsync(q =>
    q.StartAt(nameof(Nodes.SupportCase))
     .IsRelatedTo(Node.FromKey(nameof(Nodes.Device), "MacBook Air"))
     .WhereString(nameof(Nodes.SupportCase), nameof(Nodes.SupportCase.Status), "Open")
     .SortByTimestamp(oldestFirst: false)
     .Take(20)
     .Emit("N"));

var cases = r.GetEmitted("N");

Exploring Data After Ingestion

Use the workspace Shell (Manage > Shell) to verify and explore ingested data. The shell uses the full backend IQuery with all materialization methods:

return Q().StartAt(N.Device.Type).Take(10).Emit("N");
return Q().StartAt(N.Manufacturer.Type).Out(N.Part.Type).Emit("N");
return Q().EmitSummary();
return Q().StartAt(N.Part.Type).EmitNeighborsSummary();

For the full query reference, see Graph Query Language.

Referenced by

© 2026 Curiosity. All rights reserved.