#
Graph
The Mosaik.GraphDB.Safe.Graph class (often referred to simply as Graph) is the primary way to interact with the Curiosity Graph Database in custom code. It wraps the low-level GraphDB instance to provide thread safety and, most importantly, mechanisms to prevent deadlocks when modifying the graph.
#
Purpose
Curiosity's graph database is highly concurrent. When multiple operations attempt to modify the same nodes simultaneously, there is a risk of deadlocks (e.g., Process A locks Node 1 and waits for Node 2, while Process B locks Node 2 and waits for Node 1).
Graph enforces patterns that help avoid these situations or detect them early.
#
Key Methods
#
Creating and Locking Nodes
To modify a node (update properties, add edges), you must first acquire a lock on it. You can retrieve an existing node or create a new one.
1. Standard Node (Deterministic Key) Use this when you have a natural key (e.g., product code, email, username) and want to ensure the node is unique for that key.
// Get or create a node with a specific Type and Key
var productNode = await Graph.GetOrAddLockedAsync("Product", "P-12345");
// Set properties
productNode.SetString("Name", "Super Gadget");
productNode.SetDecimal("Price", 99.99m);
2. Internal Node (Generated Key) Use this when you need a node but don't have a natural key (e.g., a "Transaction" or "Log" where ID doesn't matter).
// Create a node with a random GUID key
var logNode = await Graph.GetOrAddLockedAsync("LogEntry", Guid.NewGuid().ToString());
logNode.SetString("Message", "Operation started");
logNode.SetTime("Timestamp", Time.Now);
3. Lock Existing Node Use this when you only want to update a node if it already exists.
// Try to get an existing node and lock it (returns null if not found)
var lockedNode = await Graph.TryGetLockedAsync(someUID);
if (lockedNode != null)
{
// Update...
}
#
Managing Edges
Edges are managed through the LockedNode instance. You can add unique edges (recommended), standard edges (allows duplicates), or remove them.
Adding Edges
// 1. Link to another LockedNode (safest and easiest)
var (product, category) = await Graph.GetOrAddLockedAsync("Product", "P-12345", "Category", "Electronics");
product.AddUniqueEdge("CATEGORY", category);
// 2. Link to a target by UID (if you don't need to lock the target)
// You need the target's UID and its numeric Type UID.
var manufacturerUID = Node.GetUID("Manufacturer", "AcmeCorp");
var manufacturerTypeUID = Graph.GetNodeTypeUID("Manufacturer");
product.AddUniqueEdge("MANUFACTURED_BY", manufacturerUID, manufacturerTypeUID);
Removing Edges
// Remove a specific edge type to a target
product.RemoveUniqueEdge("CATEGORY", category);
// Remove all edges of any type to a target
product.RemoveAllEdgesTo(category.UID);
#
Committing and Abandoning Changes
Once you have modified a LockedNode, you must commit the changes to release the lock and persist the data. If an error occurs, you must abandon the changes to release the lock immediately.
Recommended Pattern
var node = await Graph.GetOrAddLockedAsync("Product", "P-12345");
try
{
// Perform updates
node.SetString("Status", "InStock");
node.AddUniqueEdge("STORED_IN", warehouseUID, warehouseTypeUID);
// Commit changes
await Graph.CommitAsync(node);
}
catch (Exception ex)
{
// Release the lock without saving
Graph.AbandonChanges(node);
throw; // Re-throw if needed
}
#
Batch Operations
When you need to perform many small updates to a single node (perhaps from different parts of your logic) without committing immediately each time, you can use batching.
The Batch method queues an action to be performed on a node. The operations are automatically grouped by UID, ensuring that when the batch is committed, all operations for a specific node are applied in a single lock-update-commit cycle.
// Queue an operation (does not execute immediately)
Graph.Batch(someUID, (lockedNode) => {
lockedNode.UpdateProperty("status", "active");
});
Graph.Batch(someUID, (lockedNode) => {
lockedNode.AddEdge("processed_by", userUID);
});
// MANDATORY: Commit all batched operations
// This will lock nodes one by one, apply all queued actions, and commit.
await Graph.CommitBatchAsync();
warning Important
If you do not call `CommitBatchAsync()`, none of the batched operations will be executed.
#
Read Operations
Graph exposes all standard reading methods.
Get(uid)/TryGet(uid, out node): Retrieve read-only nodes.GetWithEdges(uid): Retrieve node with edge data.Query(): Start a query.
#
Best Practices
- Always Commit: Ensure every
GetOrAddLockedAsyncis paired with aCommitAsync(orAbandonChangesin error cases). - Order of Locks: If locking multiple nodes, try to always lock them in a consistent order (e.g., by UID) to reduce deadlock risk.
- Short Critical Sections: Keep the time between acquiring a lock and committing it as short as possible. Do expensive computations before locking if possible.