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.