Curiosity

Migration Examples

This page provides common examples of migration scripts. You can adapt these to fit your specific data model and requirements.

1. Updating Data

This example iterates through all Person nodes and normalizes the Email property to lowercase.

// Report indefinite progress initially
Progress.Indeterminate();

var query = Q().StartAt("Person");
var total = query.Count();
var count = 0;

// Iterate using AsTypedUIDEnumerable() for better memory management with large datasets
// This returns TypedUID128 structs which contain both the UID and the Type ID
foreach (var uid in query.AsTypedUIDEnumerable())
{
    // Always check for cancellation
    CancellationToken.ThrowIfCancellationRequested();

    // Lock the node for update
    var node = await Graph.TryGetLockedAsync(uid.UID);
    if (node != null)
    {
        var email = node.GetString("Email");

        // Check if update is needed
        if (!string.IsNullOrEmpty(email) && email.Any(char.IsUpper))
        {
            node["Email"] = email.ToLowerInvariant();

            // Commit the change
            await Graph.CommitAsync(node);
        }
    }

    count++;
    // Report progress periodically
    if (count % 100 == 0)
    {
        Progress.Set(count, total).Message($"Processed {count}/{total}");
    }
}

Progress.Done();

2. Adding Edges Based on Data

This example creates a relationship between Ticket and Person nodes based on an email address stored in the ticket.

Progress.Indeterminate();

var query = Q().StartAt("Ticket");
var total = query.Count();
var count = 0;

foreach (var uid in query.AsTypedUIDEnumerable())
{
    CancellationToken.ThrowIfCancellationRequested();

    var ticketNode = await Graph.TryGetLockedAsync(uid.UID);
    if (ticketNode != null)
    {
        var assigneeEmail = ticketNode.GetString("AssigneeEmail");
        if (!string.IsNullOrEmpty(assigneeEmail))
        {
            // Find the corresponding Person node (read-only query)
            // Note: Assuming "Email" is indexed or unique
            var personNode = Q().StartAt("Person").Where("Email", assigneeEmail).FirstOrDefault();

            if (personNode != null)
            {
                // Create a unique edge "AssignedTo" from Ticket to Person
                // AddUniqueEdgeTo ensures no duplicate edges of this type to the same target
                ticketNode.AddUniqueEdgeTo("AssignedTo", personNode.UID);

                await Graph.CommitAsync(ticketNode);
            }
        }
    }

    count++;
    if (count % 100 == 0)
    {
        Progress.Set(count, total).Message($"Processed {count}/{total}");
    }
}

Progress.Done();

3. Search and Queue for Re-indexing

This example uses a similarity search to find nodes related to a specific topic and queues them for file content re-extraction. This is useful if you want to re-process specific files with updated extraction logic or OCR settings.

Progress.Indeterminate();

// Search for File nodes containing the text "contract updates"
// StartSearch uses the full-text index
var query = Q().StartSearch("File", "", Search.Parse("contract updates"));
var total = query.Count();
var count = 0;

foreach (var uid in query.AsTypedUIDEnumerable())
{
    CancellationToken.ThrowIfCancellationRequested();

    // Queue the file for re-indexing (content extraction)
    // Accessing 'Internals' is necessary to reach the low-level IndexManager
    Graph.Internals.Indexes.Enqueue(uid.Type, uid.UID);

    count++;
    if (count % 10 == 0)
    {
        Progress.Set(count, total).Message($"Queued {count}/{total}");
    }
}

Progress.Done();
© 2026 Curiosity. All rights reserved.