#
Migrating from Cypher to Curiosity
If you are familiar with Cypher (the query language used by Neo4j and others), this guide will help you translate your knowledge to Curiosity's C# IQuery fluent interface.
This guide covers the query language as available in Workspace Customizations (Endpoints, Shell, AI Tools, Code Tasks, etc.). Data Connectors have access to a limited subset of this query language, optimized for referencing existing data during ingestion jobs.
While Cypher is a declarative text-based language, IQuery is a programmatic builder. However, the conceptual steps (Start -> Filter -> Traverse -> Return) are very similar.
#
Basic Lookups
#
1. Get all nodes of a label
Cypher
MATCH (n:Person) RETURN n
Curiosity
Query().StartAt("Person").Emit()
#
2. Get a node by ID
Cypher
MATCH (n) WHERE id(n) = '123' RETURN n
Curiosity
Query().StartAt("Person", "123").Emit()
#
3. Get a node by Key/Property
Cypher
MATCH (n:Person {email: 'alice@example.com'}) RETURN n
Curiosity
Query().StartAt("Person", "email", "alice@example.com").Emit()
// OR
Query().StartAt("Person").WhereString("email", "alice@example.com").Emit()
#
Filtering
#
4. Filter by string property
Cypher
MATCH (n:Person) WHERE n.status = 'Active' RETURN n
Curiosity
Query().StartAt("Person").WhereString("status", "Active").Emit()
#
5. Filter by numeric property
Cypher
MATCH (n:Product) WHERE n.price > 100 RETURN n
Curiosity
Query().StartAt("Product").Where(n => n.GetFloat("price") > 100).Emit()
#
6. Filter by boolean
Cypher
MATCH (n:Task) WHERE n.completed = true RETURN n
Curiosity
Query().StartAt("Task").Where(n => n.GetBool("completed")).Emit()
#
7. Filter NOT equal
Cypher
MATCH (n:Person) WHERE n.status <> 'Deleted' RETURN n
Curiosity
Query().StartAt("Person").WhereNotString("status", "Deleted").Emit()
#
8. Filter IN list
Cypher
MATCH (n:Person) WHERE n.role IN ['Admin', 'Manager'] RETURN n
Curiosity
Query().StartAt("Person").WhereString("role", new[]{"Admin", "Manager"}).Emit()
#
9. Filter by Timestamp
Cypher
MATCH (n:Log) WHERE n.timestamp >= datetime('2023-01-01') RETURN n
Curiosity
Query().StartAt("Log").WhereTimestamp(from: new Time(2023, 1, 1), to: Time.MaxValue, insideBoundary: true).Emit()
#
Relationships
#
10. Simple Traversal
Cypher
MATCH (p:Person)-[:KNOWS]->(f:Person) RETURN f
Curiosity
Query().StartAt("Person").Out("Person", "KNOWS").Emit()
#
11. Traversal ignoring edge type
Cypher
MATCH (p:Person)-->(d:Department) RETURN d
Curiosity
Query().StartAt("Person").Out("Department").Emit()
#
12. Traversal ignoring target label
Cypher
MATCH (p:Person)-[:HAS_FILE]->(f) RETURN f
Curiosity
Query().StartAt("Person").Out(edgeType: "HAS_FILE").Emit()
#
13. Multi-hop Traversal
Cypher
MATCH (p:Person)-[:KNOWS*2]->(fof:Person) RETURN fof
Curiosity
Query().StartAt("Person").OutMany(levels: 2, nodeTypes: new[] { "Person" }, edgeTypes: new[] { "KNOWS" }).Emit()
#
14. Check if related to specific node
Cypher
MATCH (p:Person) WHERE (p)-[:WORKS_FOR]->(:Company {id: 'C1'}) RETURN p
Curiosity
Query().StartAt("Person").IsRelatedToVia(Node.GetUID("Company", "C1"), "WORKS_FOR").Emit()
#
15. Check if NOT related
Cypher
MATCH (p:Person) WHERE NOT (p)-[:HAS_Badge]->() RETURN p
Curiosity
Query().StartAt("Person").IsNotRelatedToVia(edgeType: "HAS_Badge").Emit()
#
Advanced Logic
#
16. Union
Cypher
MATCH (n:Manager) RETURN n
UNION
MATCH (n:Director) RETURN n
Curiosity
var q1 = Query().StartAt("Manager");
var q2 = Query().StartAt("Director");
q1.Union(q2).Emit();
#
17. Intersection
Cypher
MATCH (n:Person) WHERE n.age > 30 AND n.status = 'Active' RETURN n
// (Implicit intersection of filters)
Curiosity
// Fluent chaining acts as intersection
Query().StartAt("Person").Where(n => n.GetInt("age") > 30).WhereString("status", "Active").Emit()
// Or explicit set intersection
var q1 = Query().StartAt("Person").Where(n => n.GetInt("age") > 30);
var q2 = Query().StartAt("Person").WhereString("status", "Active");
q1.Intersect(q2).Emit();
#
18. Sorting and Limiting
Cypher
MATCH (n:Product) RETURN n ORDER BY n.created DESC LIMIT 10
Curiosity
Query().StartAt("Product").SortByTimestamp(oldestFirst: false).Take(10).Emit()
#
19. Counting
Cypher
MATCH (n:Person) RETURN count(n)
Curiosity
int count = Query().StartAt("Person").Count();
#
20. Distinct
Cypher
MATCH (a)-[:FRIEND]->(b) RETURN DISTINCT b
Curiosity
Query().StartAt("Person").Out("Person", "FRIEND").Distinct().Emit()
#
21. Path Finding (Shortest Path)
Cypher
MATCH p=shortestPath((a:Person {id:'A'})-[*]-(b:Person {id:'B'})) RETURN p
Curiosity
Query().PathBetween(Node.GetUID("Person", "A"), Node.GetUID("Person", "B"))
#
22. Text Search
Cypher
MATCH (d:Document) WHERE d.content CONTAINS 'project' RETURN d
Curiosity
Query().StartSearch("Document", "content", Search.Parse("project")).Emit()
#
Complex Scenarios
#
23. Vector / Similarity Search
Cypher
// Using Vector Index (syntax varies by implementation)
CALL db.index.vector.queryNodes('embeddings', 10, $queryVector)
YIELD node, score
RETURN node, score
Curiosity
Query().StartAtSimilarText("Machine Learning applications").Emit()
#
24. Grouping and Aggregation (LINQ)
Cypher
MATCH (p:Person)
RETURN p.department, count(p) as Count
// Cypher implicitly groups by non-aggregate fields (here, p.department)
Curiosity
// Curiosity queries stream results, so grouping is done in-memory via LINQ
Query().StartAt("Person")
.Emit()
.AsEnumerable()
.GroupBy(n => n.GetString("department"))
.Select(g => new { Department = g.Key, Count = g.Count() });
#
25. Custom Logic / User Defined Functions
Cypher
// Using plugins like APOC for advanced logic
RETURN apoc.date.format(timestamp(), 'ms', 'yyyy-MM-dd')
// Or using CASE statements
MATCH (p:Person)
RETURN p.name,
CASE
WHEN p.salary > 100000 THEN 'High'
ELSE 'Standard'
END as SalaryBand
Curiosity
// Use standard C# methods or local functions
string CalculateSalaryBand(Node n) {
return n.GetFloat("salary") > 100000 ? "High" : "Standard";
}
var results = Query().StartAt("Person")
.Emit()
.AsEnumerable()
.Select(n => new {
Name = n.GetString("name"),
SalaryBand = CalculateSalaryBand(n)
});
#
26. Modifying Data
To modify data, you use the ITransactionQuery interface (accessed via .Tx()) or Graph methods directly for atomic operations.
#
Create or Update a Node
Cypher
MERGE (p:Person {email: 'alice@example.com'})
SET p.name = 'Alice'
Curiosity
// GetOrAdd (Atomic Upsert)
var node = await Graph.GetOrAddLockedAsync("Person", "alice@example.com");
node.Set("name", "Alice");
await Graph.CommitAsync(node);
#
Update Properties on Query Results
Cypher
MATCH (p:Person) WHERE p.status = 'Old'
SET p.status = 'Inactive'
Curiosity
// Update all matching nodes
await Query().StartAt("Person")
.WhereString("status", "Old")
.Tx()
.Set("status", "Inactive")
.CommitAsync();
#
Create Relationships
Cypher
MATCH (p:Person {id: '1'}), (c:Company {id: 'C1'})
CREATE (p)-[:WORKS_FOR]->(c)
Curiosity
await Query().StartAt("Person", "1")
.Tx()
.AddUniqueEdgeTo("WORKS_FOR", "Company", "C1")
.CommitAsync();
#
Delete Nodes
Cypher
MATCH (n:Data) WHERE n.obsolete = true
DETACH DELETE n
Curiosity
await Query().StartAt("Data")
.Where(n => n.GetBool("obsolete"))
.Tx()
.Delete()
.CommitAsync();