{"record":{"id":"73ee309ffce01dad","repo":"dotnet/efcore","slug":"the-optional-complex-property-type-property","errorCode":null,"errorMessage":"The optional complex property '{type}.{property}' is mapped to columns by flattening the contained properties into its container's table; this mapping requires at least one required property - to allow distinguishing between 'null' and empty values - but the complex type contains only optional properties. Configure the property with a shadow discriminator by adding a call to 'HasDiscriminator()' on the complex property configuration, or map this complex property to a JSON column instead.","messagePattern":"The optional complex property '(.+?)\\.(.+?)' is mapped to columns by flattening the contained properties into its container's table; this mapping requires at least one required property - to allow distinguishing between 'null' and empty values - but the complex type contains only optional properties\\. Configure the property with a shadow discriminator by adding a call to 'HasDiscriminator\\(\\)' on the complex property configuration, or map this complex property to a JSON column instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs","lineNumber":278,"sourceCode":"    /// <inheritdoc />\n    protected override void ValidatePropertyMapping(\n        IComplexProperty complexProperty,\n        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)\n    {\n        base.ValidatePropertyMapping(complexProperty, logger);\n\n        if (complexProperty.IsCollection && !complexProperty.ComplexType.IsMappedToJson())\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.ComplexCollectionNotMappedToJson(\n                    complexProperty.DeclaringType.DisplayName(), complexProperty.Name));\n        }\n\n        if (!complexProperty.ComplexType.IsMappedToJson()\n            && complexProperty.IsNullable\n            && complexProperty.ComplexType.GetProperties().All(m => m.IsNullable))\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.ComplexPropertyOptionalTableSharing(complexProperty.ComplexType.DisplayName(), complexProperty.Name));\n        }\n\n        if (complexProperty.GetJsonPropertyName() != null)\n        {\n            if (complexProperty.ComplexType.FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string columnName)\n            {\n                throw new InvalidOperationException(\n                    RelationalStrings.ComplexPropertyBothJsonColumnAndJsonPropertyName(\n                        $\"{complexProperty.DeclaringType.DisplayName()}.{complexProperty.Name}\",\n                        columnName,\n                        complexProperty.GetJsonPropertyName()));\n            }\n\n            if (!complexProperty.DeclaringType.IsMappedToJson())\n            {\n                throw new InvalidOperationException(\n                    RelationalStrings.ComplexPropertyJsonPropertyNameWithoutJsonMapping(","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs#L260-L296","documentation":"ValidatePropertyMapping throws ComplexPropertyOptionalTableSharing when an optional (nullable) complex property is mapped by flattening its contained properties into the container's table, but every contained property is also optional. Without at least one required property, EF cannot distinguish a null complex instance from an all-null but present instance. The message prescribes HasDiscriminator() on the complex property or mapping to JSON instead.","triggerScenarios":"Configuring OwnsOne with IsRequired(false) where the complex type contains only nullable properties and no JSON mapping. The validator checks `!IsMappedToJson && IsNullable && ComplexType.GetProperties().All(m => m.IsNullable)`.","commonSituations":"Address-like value objects whose every field (Street, City, Zip) is nullable, configured as optional; refactoring from JSON back to column flattening and losing the discriminator; using IsRequired(false) globally on complex types.","solutions":["Add a required sentinel property and call HasDiscriminator() on the complex property configuration.","Map the complex property to a JSON column via ToJson(), which can represent null vs empty natively.","Make at least one contained property required (e.g. a non-nullable IsPresent flag)."],"exampleFix":"// before — all address fields nullable, complex prop optional\nmodelBuilder.Entity<Customer>()\n    .OwnsOne(c => c.Address, a =>\n    {\n        a.Property(p => p.Street).IsRequired(false);\n        a.Property(p => p.City).IsRequired(false);\n    }); // throws\n\n// after — map to JSON so null vs empty is unambiguous\nmodelBuilder.Entity<Customer>()\n    .OwnsOne(c => c.Address, a => a.ToJson(\"address\"));","handlingStrategy":"validation","validationCode":"if (!complexProperty.ComplexType.IsMappedToJson()\n    && complexProperty.IsNullable\n    && complexProperty.ComplexType.GetProperties().All(m => m.IsNullable))\n    throw new InvalidOperationException(\"Optional complex type with all-optional properties needs a discriminator or JSON mapping.\");","typeGuard":"static bool NeedsDiscriminatorOrJson(IComplexProperty cp)\n    => !cp.ComplexType.IsMappedToJson() && cp.IsNullable\n       && cp.ComplexType.GetProperties().All(m => m.IsNullable);","tryCatchPattern":null,"preventionTips":["Add HasDiscriminator() on optional complex properties whose members are all nullable.","Prefer ToJson() for optional complex types when null vs empty must be distinguishable.","Make at least one contained property required to act as a sentinel."],"tags":["complex-type","optional","table-sharing","discriminator","model-validation"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}