# 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();