Skip to content

Health Check: Add check for untrusted database constraints on SQL Server#22592

Merged
AndyButland merged 2 commits intomainfrom
v17/feature/healthcheck-for-un-trusted-constraints
Apr 26, 2026
Merged

Health Check: Add check for untrusted database constraints on SQL Server#22592
AndyButland merged 2 commits intomainfrom
v17/feature/healthcheck-for-un-trusted-constraints

Conversation

@AndyButland
Copy link
Copy Markdown
Contributor

@AndyButland AndyButland commented Apr 24, 2026

Description

The RetrustForeignKeyAndCheckConstraints migration introduced in 17.3 (see #21744 and #22488) re-trusts untrusted foreign key and check constraints so the SQL Server query optimizer can use them. Existing data integrity violations are logged as a warning and the migration continues.

Although there's a warning in the upgrade log there is no other in-product signal of an issue.

This PR adds a new health check — "Untrusted database constraints" — in the existing Data Integrity group that surfaces this state and links to documentation for manual resolution.

The check does the same SQL Server query to find untrusted constraints that the migration does (limited to Umbraco tables).

It then reports details on any untrusted constraints found:

image

Or gives a success message if none are found:

image

There's no automatic fix to the constraints as it's likely orphaned records need to be removed (otherwise the migration would have fixed them). Instead there's a link to a docs page that needs to be written and the short-link setup.

Testing

Automated

Integration tests are added for the health-check. Set Tests:Database:DatabaseType in tests/Umbraco.Tests.Integration/appsettings.Tests.json to "LocalDb" to exercise it:

dotnet test tests/Umbraco.Tests.Integration --filter "FullyQualifiedName~UntrustedDatabaseConstraintsCheckTests"

Manual

Run a site against SQL Server.

1. Verify the happy path (no untrusted constraints).

  • Log into the backoffice → Settings → Health Check → Data Integrity.
  • Confirm "Untrusted database constraints" appears alongside the existing "Database data integrity check".
  • Run it — should report Success, message: "All Umbraco database constraints are trusted."

2. Create an untrusted constraint (flips the trust flag without needing orphaned data):

ALTER TABLE [umbracoRelation] NOCHECK CONSTRAINT [FK_umbracoRelation_umbracoNode];
ALTER TABLE [umbracoRelation] WITH NOCHECK CHECK CONSTRAINT [FK_umbracoRelation_umbracoNode];

-- Verify:
SELECT name, is_not_trusted FROM sys.foreign_keys WHERE name = 'FK_umbracoRelation_umbracoNode';
-- Expected: is_not_trusted = 1

Re-run the health check. Expected:

  • Result: Warning.
  • Message: "Found 1 untrusted constraint(s). These indicate pre-existing data integrity issues that need to be resolved manually."
  • Description lists Foreign key FK_umbracoRelation_umbracoNode on dbo.umbracoRelation.
  • "Read more" link is rendered and links to a short-link.

3. Restore trust (returns the check to Success):

ALTER TABLE [umbracoRelation] WITH CHECK CHECK CONSTRAINT [FK_umbracoRelation_umbracoNode];

-- Verify:
SELECT name, is_not_trusted FROM sys.foreign_keys WHERE name = 'FK_umbracoRelation_umbracoNode';
-- Expected: is_not_trusted = 0

Re-run the health check — should return to Success.

4. Re-check the migration.

Prepare an untrusted constraint again (step 2).

Reset the migration state to the step prior to the trust constraints migration:

UPDATE umbracoKeyValue
SET [value] = '{B2F4A1C3-8D5E-4F6A-9B7C-3E1D2A4F5B6C}'
WHERE [key] = 'Umbraco.Core.Upgrader.State+Umbraco.Core';

Restart Umbraco.

Re-run the health check — should show Success.

Copilot AI review requested due to automatic review settings April 24, 2026 10:01
@claude
Copy link
Copy Markdown

claude Bot commented Apr 24, 2026

Claude finished @AndyButland's task in 5m 54s —— View job


PR Review

Target: origin/main · Based on commit: 92131de3

Adds a "Untrusted database constraints" health check to the Data Integrity group that queries sys.foreign_keys and sys.check_constraints on SQL Server for is_not_trusted = 1 entries, short-circuiting to Info on SQLite or when the database is unreachable.

  • Other changes: New Constants.HealthChecks.DocumentationLinks.Data.UntrustedConstraintsCheck constant and a new auto-discovered UntrustedDatabaseConstraintsCheck class visible to plugin developers and host applications.

Suggestions

  • tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/HealthChecks/UntrustedDatabaseConstraintsCheckTests.cs:23,38,62: Test method names (FreshDatabase_ReportsSuccessOnSqlServer, UntrustedConstraint_ReportsWarningAndIncludesConstraintName, SqliteDatabase_ReportsInfoShortCircuit) don't follow the established patterns. The coding conventions specify Can_/Cannot_ with PascalCase underscore-separated words (e.g. Can_Detect_Untrusted_FK_On_SqlServer). The closest sibling (ImagingHMACSecretKeyCheckTests) uses MethodName_WhenCondition_ReturnsExpected (e.g. GetStatusAsync_WhenConstraintIsUntrusted_ReturnsWarning). Either convention is acceptable, but the current names mix the two without a clear separator between condition and expected result. Fix this →

Notes

A few observations that don't warrant findings but are worth calling out for context:

  • The #pragma warning disable CS0618 around db.DatabaseType.IsSqlServer() is correct and the justification comment is good. The existing callers of that same method (DatabaseReadOnlyAccessor, UmbracoDatabase) omit the pragma only because CS0618 is in WarningsNotAsErrors project-wide — making the pragma explicit here is actually better practice.
  • The private UntrustedConstraintDto duplicates a similar DTO in RetrustForeignKeyAndCheckConstraints but adds the ConstraintType column needed for the user-facing description. This is appropriate given both are private to their respective classes.
  • _databaseFactory.CanConnect performs an actual connection attempt, meaning the check can make two round-trips (one for the guard, one for the query). This is consistent with how other health checks in the codebase use this property.

Approved with Suggestions for improvement

Good to go, but please carefully consider the importance of the suggestions.

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

Adds a new Data Integrity health check that surfaces untrusted SQL Server FK/check constraints (left behind when re-trust migration skips violated constraints), including a documentation “Read more” link and integration coverage.

Changes:

  • Added UntrustedDatabaseConstraintsCheck health check that queries sys.foreign_keys / sys.check_constraints for is_not_trusted = 1 on umbraco% / cms% tables and reports Success/Warning/Info accordingly.
  • Added a documentation-link constant for the new health check.
  • Added integration tests covering: fresh DB (Success), deliberately untrusted FK (Warning), and SQLite short-circuit (Info).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/Umbraco.Infrastructure/HealthChecks/Checks/Data/UntrustedDatabaseConstraintsCheck.cs Implements the new SQL Server-only health check and renders a per-constraint HTML description + read-more link.
src/Umbraco.Core/Constants-HealthChecks.cs Adds the documentation shortlink constant used by the new health check.
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/HealthChecks/UntrustedDatabaseConstraintsCheckTests.cs Adds integration tests validating Success/Warning/Info outcomes across SQL Server vs SQLite.

Copy link
Copy Markdown
Contributor

@kjac kjac left a comment

Choose a reason for hiding this comment

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

This is excellent, @AndyButland 💪

The short-link does not work, though - https://umbra.co/healthchecks-untrusted-constraints yields a 404. But if that's to be expected at this point, feel free to merge this in 👍

@AndyButland
Copy link
Copy Markdown
Contributor Author

Thanks @kjac.

The short-link does not work, though - https://umbra.co/healthchecks-untrusted-constraints yields a 404.

Yes, that's expected. I've raised umbraco/UmbracoDocs#8004 for the docs, so when that is merged in - and before 17.5 - we can prepare the short-link.

@AndyButland AndyButland merged commit e6cba5e into main Apr 26, 2026
27 of 28 checks passed
@AndyButland AndyButland deleted the v17/feature/healthcheck-for-un-trusted-constraints branch April 26, 2026 07:50
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