Skip to content

Migrations: Fix property detection for invariant content types with culture-varying compositions (closes #22159)#22167

Merged
Migaroez merged 6 commits intomainfrom
v17/bugfix/22159-fix-property-detection-logic-in-convert-migrations
Mar 19, 2026
Merged

Migrations: Fix property detection for invariant content types with culture-varying compositions (closes #22159)#22167
Migaroez merged 6 commits intomainfrom
v17/bugfix/22159-fix-property-detection-logic-in-convert-migrations

Conversation

@AndyButland
Copy link
Copy Markdown
Contributor

@AndyButland AndyButland commented Mar 18, 2026

Summary

This PR addresses the issue raised in #22159, where where V15+ upgrade migrations that convert property data would skip on invariant content types that use culture-varying compositions.

We do this in four migrations, so I've extracted it into a shared, testable helper (PropertyDataCultureResolver).

Then I could add unit tests covering all resolution paths, including the bug scenario.

Problem

When upgrading to V15+, four migrations (ConvertBlockEditorProperties, ConvertLocalLinks, FixConvertLocalLinks, MigrateSingleBlockList) resolve the culture for each property data row before processing it. The resolution logic had a bug in its skip condition:

if (culture is null && propertyType.VariesByCulture())

This skipped all rows where culture resolved to null and the property type varied by culture. But there is a legitimate case where both conditions are true yet the row should be processed: when an invariant content type composes a culture-varying content type. In that scenario:

  • The property type reports VariesByCulture() == true (inherited from the composition).
  • The property data has languageId = NULL (correct — the content type is invariant).
  • Culture resolves to null because languageId is null.
  • The migration incorrectly treats this as a deleted-language scenario and skips the row.

The intended skip case is when languageId is non-null but references a language that no longer exists in the database.

Fix

Add && propertyDataDto.LanguageId.HasValue to the skip condition, distinguishing:

languageId VariesByCulture Meaning Action
NULL true Invariant data on a culture-varying composition Process
Non-null, found true Normal culture-varying data Process
Non-null, not found true Deleted language reference Skip with warning

Additional Fix

When manually testing, I found that when PropertyDataCultureResolver correctly stops skipping invariant rows, Property.SetValue would throw NotSupportedException because the property type reports VariesByCulture but culture is null. To resolve I've added a CreateMigrationProperty helper that, instead of creating a new Property deep-clones the property type with Variations=Nothing for this case, keeping the original unmodified for thread safety during parallel migration execution.

Refactoring

The identical culture-resolution + skip logic was duplicated across 4 migration files. This PR extracts it into PropertyDataCultureResolver.ResolveCulture(), a pure static method that returns a result struct. This:

  • Eliminates duplication — the logic now exists in one place
  • Enables unit testing — the method takes IPropertyType, int?, and IDictionary<int, ILanguage>, making it trivially testable with mocks and no infrastructure dependencies
  • Consolidates the warning log template — a shared const replaces 4 copies of the same string

Testing

Automated

Unit tests have been added in PropertyDataCultureResolverTests which tests the code around the bug. The expected ones failed before fix.

An integration test for the migration didn't prove feasible to setup.

Manual testing steps

  1. In a 13 site, create a composition content type with Vary by culture enabled.
  2. Add a Block List property to the composition, set to Vary by culture.
  3. Create an invariant content type that uses the composition.
  4. Create content under the invariant type and populate the block list / rich text property.
  5. Backup the database so you have a copy.
  6. Upgrade the database to 17 using code from main.
  7. Check the logs and note the property data is not processed (skipped with a "references a language that does not exist" warning).
  8. Repeat using code from this PR.
  9. Check the logs and verify the property data is processed (not skipped with "references a language that does not exist" warning).

…tiesBase, ConvertLocalLinks, FixConvertLocalLinks, and MigrateSingleBlockList into PropertyDataCultureResolver, fixing a bug where NULL languageId (legitimate invariant data) was incorrectly treated as a deleted language reference.

Add unit tests covering all resolution paths including the bug scenario.
Copilot AI review requested due to automatic review settings March 18, 2026 11:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an upgrade-migration edge case where property data rows for invariant content types were incorrectly skipped when the property type varies by culture via compositions, and centralizes culture-resolution logic into a shared helper with unit test coverage.

Changes:

  • Fix culture-varying skip logic so rows are only skipped when languageId is non-null but missing from the language lookup (orphaned/deleted language scenario).
  • Refactor duplicated culture-resolution + warning-template logic into PropertyDataCultureResolver.
  • Add unit tests covering invariant/culture-varying and orphaned-language scenarios (including the reported bug case).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PropertyDataCultureResolverTests.cs Adds unit tests for all ResolveCulture decision paths, including the invariant-content / culture-varying-composition bug scenario.
src/Umbraco.Infrastructure/Migrations/Upgrade/V_18_0_0/MigrateSingleBlockList.cs Switches migration logic to the shared resolver and centralized warning template when encountering orphaned languages.
src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_1_0/FixConvertLocalLinks.cs Replaces duplicated culture-resolution logic with PropertyDataCultureResolver.
src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertLocalLinks.cs Replaces duplicated culture-resolution logic with PropertyDataCultureResolver.
src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockEditorPropertiesBase.cs Replaces duplicated culture-resolution logic with PropertyDataCultureResolver inside batch processing.
src/Umbraco.Infrastructure/Migrations/PropertyDataCultureResolver.cs Introduces the shared resolver + structured result and a shared warning template constant.

You can also share your feedback on Copilot code review. Take the survey.

@Migaroez
Copy link
Copy Markdown
Contributor

Code LGTM

Copy link
Copy Markdown
Contributor

@Migaroez Migaroez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests out as expected.

@Migaroez Migaroez merged commit b7f8a62 into main Mar 19, 2026
26 of 27 checks passed
@Migaroez Migaroez deleted the v17/bugfix/22159-fix-property-detection-logic-in-convert-migrations branch March 19, 2026 09:11
AndyButland added a commit that referenced this pull request Mar 19, 2026
…ulture-varying compositions (closes #22159) (#22167)

* Extract shared culture-resolution logic from ConvertBlockEditorPropertiesBase, ConvertLocalLinks, FixConvertLocalLinks, and MigrateSingleBlockList into PropertyDataCultureResolver, fixing a bug where NULL languageId (legitimate invariant data) was incorrectly treated as a deleted language reference.

Add unit tests covering all resolution paths including the bug scenario.

* Remove obsoletion on helper.

* Address code review feedback.

* Handle SetValue variation mismatch for invariant data on culture-varying compositions

* Fixed build error in tests.

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
@AndyButland
Copy link
Copy Markdown
Contributor Author

Cherry-picked into release/17.3.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants