Content Migration Tip 2 Handling Duplicates in Sitecore Serialization

Created: 24 Mar 2025, last update: 25 Mar 2025

Content Migration Tip 2 - Handling Duplicates in Sitecore Serialization

Sitecore serialization does not work when multiple items in the same folder have the same name.

You may encounter errors like this when running:

dotnet sitecore ser pull
/sitecore/content/Home/errorstuff/duplicated/dupitem (b05abcba-d45c-4887-93cf-65f9f9b6ce8b) attempted to be cached, but the cache already contained /sitecore/content/Home/errorstuff/duplicated/dupitem with a different ID (c5715044-e4e7-4228-98e0-9b19dde376d3). 

Non-unique paths cannot be serialized. Please choose a different item name.

In Sitecore content migration - Part 1: Media analysis, I briefly mentioned this issue and referenced the PowerShell report "Items with Duplicate Names" under Content Audit. I also shared a PowerShell script to rename duplicate items. However, that script relied on Axes.GetDescendants(), which consumes a lot of memory and is very slow for large content trees. Additionally, it used path-based queries, which fail if one of the parent items also has a duplicate name.

This blog provides a better script with a more complete solution, designed for databases with millions of items.

Why does this happen?
Duplicate item names often occur when migrating content from older Sitecore versions. In the past, it was easy to create duplicates, especially in the Media Library, which frequently leads to serialization issues. However, in modern Sitecore versions, the AllowDuplicateItemNamesOnSameLevel setting prevents this, making it primarily a migration-related problem.

Script Approach
The script directly queries the Sitecore database, which is generally not recommended. However, for a one-time migration task, this approach works efficiently. An alternative is using the search index, which is also fast and suited for large content trees. The PowerShell report "Items with Duplicate Names" provides an index-based approach to find duplicates. However, it's important to note that the search index only contains items with a version, so it only works when an item has at least one version. This is why, in this case, a SQL query is the best option. The script only reads from the database, while the renaming operation is done via the Sitecore API to ensure everything is updated correctly. Important note: Resource items are not stored in the database, so the script will not detect them.

The Sitecore PowerShell script
Finds and renames all duplicate Sitecore items within the same parent to ensure unique names.

How it works:
- Query the Sitecore database to identify items with duplicate names under the same parent.
- Filter items by a specific target path to avoid renaming unintended items.
- Generate a unique name by appending a suffix (e.g., _R1, _R2).
- Rename the duplicates using the Sitecore API to ensure proper updates.

This approach ensures that serialization and content resolution issues due to duplicate names are avoided.

Import-Function -Name Invoke-SqlCommand
$masterconnection = [Sitecore.Configuration.Settings]::GetConnectionString("master")

# Configuration: customizable suffix and path filter
$suffixPattern = "_R{0}"  # Determines the renaming pattern (_R1, _R2, ...)
$targetPath = "/sitecore/content/Home/"  # Process only items within this path

# SQL query to find duplicate names within the same ParentID
$query = @"
WITH DuplicateItems AS (
    SELECT ID, Name, ParentID, 
           ROW_NUMBER() OVER (PARTITION BY ParentID, Name ORDER BY ID) AS RowNum
    FROM Items
)
SELECT ID, Name, ParentID FROM DuplicateItems WHERE RowNum > 1 ORDER BY ParentID, Name
"@

$results = Invoke-SqlCommand -Connection $masterconnection -Query $query

# Dictionary to keep track of already renamed items per ParentID
$processedItems = @{}

$results | ForEach-Object {
    #Write-Host "ID: $($_.ID) | Name: $($_.Name) | ParentID: $($_.ParentID)"
    $item = Get-Item -Path "master:" -ID "{$($_.ID)}"

    if ($item.Paths.FullPath.StartsWith($targetPath)) {
        Write-Host "Found duplicate: $($item.Paths.FullPath)"

        $originalName = [Sitecore.Data.Items.ItemUtil]::ProposeValidItemName($item.Name)
        $parentId = $_.ParentID

        if (-not $processedItems.ContainsKey($parentId)) {
            $processedItems[$parentId] = @{}
        }

        # Determine a unique name
        $suffixCounter = 1
        $newName = $originalName + ($suffixPattern -f $suffixCounter)

        while ($processedItems[$parentId].ContainsValue($newName)) {
            $suffixCounter++
            $newName = $originalName + ($suffixPattern -f $suffixCounter)
        }

        # Update the list of processed items
        $processedItems[$parentId][$_.ID] = $newName

        # Rename the item in Sitecore
        Write-Host "Renaming: $($item.Name) -> $newName"
        $item.Editing.BeginEdit()
        $item.Name = $newName
        $item.Editing.EndEdit()
    }
}

Write-Host "Script completed!"

note: Change the $targetPath to select a specific tree.
note: With a small change, you can store the original name in the display name. I prefer not to, but if you do, be mindful of language versions.

API Solution
It is also possible to find and rename duplicate items using the Sitecore Authoring and Management GraphQL API, but this approach has the same limitations as the search index.

With the following query, you can find all duplicate names within a subtree that have an English version:

query {
  search(
    query: {
      index: "sitecore_master_index"
      searchStatement: {
         criteria: [
         {
            field: "_fullpath"
            value: "/sitecore/content/Home"
            criteriaType: STARTSWITH
        },
        {
            field: "_language"
            value: "en"
            criteriaType: EXACT
            operator:MUST
          }]
        }
      facetOnFields: ["_fullpath"]
    }
  ) {
    facets {
      name
      facets {
        name
        count
      }
    }
  }
}

Recommendation
Keep the AllowDuplicateItemNamesOnSameLevel setting in Sitecore.config set to false. If two items share the same path, Sitecore does not guarantee a consistent resolution order, and which can lead to serialization issues.

Links

Content Migration Tip 1 - Handling Clones in Sitecore Serialization
Sitecore content migration - Part 1: Media analysis
Sitecore content migration - Part 2: Media migration
Sitecore content migration - Part 3: Converting content