diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 14d0e025722..99973653785 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -475,7 +475,7 @@ private void GenerateKey(IKey key, IEntityType entityType, bool useDataAnnotatio if (explicitName) { lines.Add( - $".{nameof(RelationalKeyBuilderExtensions.HasName)}" + $"({_code.Literal(key.GetName())})"); + $".{nameof(RelationalKeyBuilderExtensions.HasName)}({_code.Literal(key.GetName())})"); } lines.AddRange( @@ -535,8 +535,7 @@ private void GenerateIndex(IIndex index) var lines = new List { - $".{nameof(EntityTypeBuilder.HasIndex)}" - + $"({_code.Lambda(index.Properties, "e")}, " + $".{nameof(EntityTypeBuilder.HasIndex)}({_code.Lambda(index.Properties, "e")}, " + $"{_code.Literal(index.GetDatabaseName())})" }; annotations.Remove(RelationalAnnotationNames.Name); @@ -587,7 +586,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) if (columnType != null) { lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}" + $"({_code.Literal(columnType)})"); + $".{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}({_code.Literal(columnType)})"); } var maxLength = property.GetMaxLength(); @@ -595,7 +594,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) if (maxLength.HasValue) { lines.Add( - $".{nameof(PropertyBuilder.HasMaxLength)}" + $"({_code.Literal(maxLength.Value)})"); + $".{nameof(PropertyBuilder.HasMaxLength)}({_code.Literal(maxLength.Value)})"); } } @@ -604,18 +603,18 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) if (precision != null && scale != null && scale != 0) { lines.Add( - $".{nameof(PropertyBuilder.HasPrecision)}" + $"({_code.Literal(precision.Value)}, {_code.Literal(scale.Value)})"); + $".{nameof(PropertyBuilder.HasPrecision)}({_code.Literal(precision.Value)}, {_code.Literal(scale.Value)})"); } else if (precision != null) { lines.Add( - $".{nameof(PropertyBuilder.HasPrecision)}" + $"({_code.Literal(precision.Value)})"); + $".{nameof(PropertyBuilder.HasPrecision)}({_code.Literal(precision.Value)})"); } if (property.IsUnicode() != null) { lines.Add( - $".{nameof(PropertyBuilder.IsUnicode)}" + $"({(property.IsUnicode() == false ? "false" : "")})"); + $".{nameof(PropertyBuilder.IsUnicode)}({(property.IsUnicode() == false ? "false" : "")})"); } var defaultValue = property.GetDefaultValue(); @@ -627,7 +626,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) else if (defaultValue != null) { lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}" + $"({_code.UnknownLiteral(defaultValue)})"); + $".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}({_code.UnknownLiteral(defaultValue)})"); annotations.Remove(RelationalAnnotationNames.DefaultValue); } @@ -715,7 +714,7 @@ private void GenerateRelationship(IForeignKey foreignKey, bool useDataAnnotation { canUseDataAnnotations = false; lines.Add( - $".{nameof(ReferenceReferenceBuilder.OnDelete)}" + $"({_code.Literal(foreignKey.DeleteBehavior)})"); + $".{nameof(ReferenceReferenceBuilder.OnDelete)}({_code.Literal(foreignKey.DeleteBehavior)})"); } if (!string.IsNullOrEmpty((string)foreignKey[RelationalAnnotationNames.Name])) @@ -800,7 +799,6 @@ private void GenerateSequence(ISequence sequence) private IList GenerateAnnotations(IEnumerable annotations) => annotations.Select( - a => - $".HasAnnotation({_code.Literal(a.Name)}, " + $"{_code.UnknownLiteral(a.Value)})").ToList(); + a => $".HasAnnotation({_code.Literal(a.Name)}, {_code.UnknownLiteral(a.Value)})").ToList(); } } diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index f04d4ec91cc..8db0d6cc743 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -758,6 +758,89 @@ private static object ConvertDefaultValue([NotNull] IProperty property, [CanBeNu public static ConfigurationSource? GetDefaultValueConfigurationSource([NotNull] this IConventionProperty property) => property.FindAnnotation(RelationalAnnotationNames.DefaultValue)?.GetConfigurationSource(); + /// + /// Gets the maximum length of data that is allowed in this property. For example, if the property is a + /// then this is the maximum number of characters. + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// The maximum length, or if none if defined. + public static int? GetMaxLength([NotNull] this IProperty property, in StoreObjectIdentifier storeObject) + { + Check.NotNull(property, nameof(property)); + + var maxLength = property.GetMaxLength(); + if (maxLength != null) + { + return maxLength.Value; + } + + var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedTableRootProperty != null ? GetMaxLength(sharedTableRootProperty, storeObject) : null; + } + + /// + /// Gets the precision of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of digits. + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// The precision, or if none is defined. + public static int? GetPrecision([NotNull] this IProperty property, in StoreObjectIdentifier storeObject) + { + Check.NotNull(property, nameof(property)); + + var precision = property.GetPrecision(); + if (precision != null) + { + return precision; + } + + var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedTableRootProperty != null ? GetPrecision(sharedTableRootProperty, storeObject) : null; + } + + /// + /// Gets the scale of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of decimal places. + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// The scale, or if none is defined. + public static int? GetScale([NotNull] this IProperty property, in StoreObjectIdentifier storeObject) + { + Check.NotNull(property, nameof(property)); + + var scale = property.GetScale(); + if (scale != null) + { + return scale.Value; + } + + var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedTableRootProperty != null ? GetScale(sharedTableRootProperty, storeObject) : null; + } + + /// + /// Gets a value indicating whether or not the property can persist Unicode characters. + /// + /// The property. + /// The identifier of the table-like store object containing the column. + /// The Unicode setting, or if none is defined. + public static bool? IsUnicode([NotNull] this IProperty property, in StoreObjectIdentifier storeObject) + { + Check.NotNull(property, nameof(property)); + + var unicode = property.IsUnicode(); + if (unicode != null) + { + return unicode.Value; + } + + var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedTableRootProperty != null ? IsUnicode(sharedTableRootProperty, storeObject) : null; + } + /// /// Returns a flag indicating if the property as capable of storing only fixed-length data, such as strings. /// diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index b8c6c3b4dc5..43401bfa320 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -690,7 +690,7 @@ protected virtual void ValidateCompatible( in StoreObjectIdentifier storeObject, [NotNull] IDiagnosticsLogger logger) { - if (property.IsNullable != duplicateProperty.IsNullable) + if (property.IsColumnNullable(storeObject) != duplicateProperty.IsColumnNullable(storeObject)) { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameNullabilityMismatch( @@ -702,8 +702,8 @@ protected virtual void ValidateCompatible( storeObject.DisplayName())); } - var currentMaxLength = property.GetMaxLength(); - var previousMaxLength = duplicateProperty.GetMaxLength(); + var currentMaxLength = property.GetMaxLength(storeObject); + var previousMaxLength = duplicateProperty.GetMaxLength(storeObject); if (currentMaxLength != previousMaxLength) { throw new InvalidOperationException( @@ -718,7 +718,7 @@ protected virtual void ValidateCompatible( currentMaxLength)); } - if (property.IsUnicode() != duplicateProperty.IsUnicode()) + if (property.IsUnicode(storeObject) != duplicateProperty.IsUnicode(storeObject)) { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameUnicodenessMismatch( @@ -742,6 +742,38 @@ protected virtual void ValidateCompatible( storeObject.DisplayName())); } + var currentPrecision = property.GetPrecision(storeObject); + var previousPrecision = duplicateProperty.GetPrecision(storeObject); + if (currentPrecision != previousPrecision) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNamePrecisionMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName(), + currentPrecision, + previousPrecision)); + } + + var currentScale = property.GetScale(storeObject); + var previousScale = duplicateProperty.GetScale(storeObject); + if (currentScale != previousScale) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameScaleMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName(), + currentScale, + previousScale)); + } + if (property.IsConcurrencyToken != duplicateProperty.IsConcurrencyToken) { throw new InvalidOperationException( diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index 48947fe4dbe..2b1ce9297a5 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -26,27 +26,27 @@ public interface IColumn : IColumnBase /// then this is the maximum number of characters. /// int? MaxLength - => PropertyMappings.First().Property.GetMaxLength(); + => PropertyMappings.First().Property.GetMaxLength(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Gets the precision of data that is allowed in this column. For example, if the property is a ' /// then this is the maximum number of digits. /// int? Precision - => PropertyMappings.First().Property.GetPrecision(); + => PropertyMappings.First().Property.GetPrecision(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Gets the scale of data that is allowed in this column. For example, if the property is a ' /// then this is the maximum number of decimal places. /// int? Scale - => PropertyMappings.First().Property.GetScale(); + => PropertyMappings.First().Property.GetScale(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Gets a value indicating whether or not the property can persist Unicode characters. /// bool? IsUnicode - => PropertyMappings.First().Property.IsUnicode(); + => PropertyMappings.First().Property.IsUnicode(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// Returns a flag indicating if the property as capable of storing only fixed-length data, such as strings. diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 51861e4f534..e10fbcf4b8e 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -356,7 +356,7 @@ public static string DuplicateColumnNameIsStoredMismatch([CanBeNull] object enti entityType1, property1, entityType2, property2, columnName, table, value1, value2); /// - /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different max lengths ('{maxLength1}' and '{maxLength2}'). + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different max lengths ('{maxLength1}' and '{maxLength2}'). /// public static string DuplicateColumnNameMaxLengthMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object maxLength1, [CanBeNull] object maxLength2) => string.Format( @@ -371,6 +371,22 @@ public static string DuplicateColumnNameNullabilityMismatch([CanBeNull] object e GetString("DuplicateColumnNameNullabilityMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), entityType1, property1, entityType2, property2, columnName, table); + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different precision ('{precision1}' and '{precision2}'). + /// + public static string DuplicateColumnNamePrecisionMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object precision1, [CanBeNull] object precision2) + => string.Format( + GetString("DuplicateColumnNamePrecisionMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(precision1), nameof(precision2)), + entityType1, property1, entityType2, property2, columnName, table, precision1, precision2); + + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different scale ('{scale1}' and '{scale2}'). + /// + public static string DuplicateColumnNameScaleMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object scale1, [CanBeNull] object scale2) + => string.Format( + GetString("DuplicateColumnNameScaleMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(scale1), nameof(scale2)), + entityType1, property1, entityType2, property2, columnName, table, scale1, scale2); + /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different unicode configuration /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 0d52bb852c3..56bfecae835 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -248,11 +248,17 @@ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different stored computed column settings ('{value1}' and '{value2}'). - '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different max lengths ('{maxLength1}' and '{maxLength2}'). + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different max lengths ('{maxLength1}' and '{maxLength2}'). '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different nullability. + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different precision ('{precision1}' and '{precision2}'). + + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different scale ('{scale1}' and '{scale2}'). + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different unicode configuration diff --git a/src/EFCore/Extensions/PropertyExtensions.cs b/src/EFCore/Extensions/PropertyExtensions.cs index 60167fdabce..1a0c8075ee7 100644 --- a/src/EFCore/Extensions/PropertyExtensions.cs +++ b/src/EFCore/Extensions/PropertyExtensions.cs @@ -194,7 +194,7 @@ public static IEnumerable GetContainingKeys([NotNull] this IProperty prope => Check.NotNull((Property)property, nameof(property)).GetContainingKeys(); /// - /// Gets the maximum length of data that is allowed in this property. For example, if the property is a ' + /// Gets the maximum length of data that is allowed in this property. For example, if the property is a /// then this is the maximum number of characters. /// /// The property to get the maximum length of. @@ -208,8 +208,7 @@ public static IEnumerable GetContainingKeys([NotNull] this IProperty prope /// /// Gets the precision of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of digits. + /// For example, if the property is a then this is the maximum number of digits. /// /// The property to get the precision of. /// The precision, or if none is defined. @@ -222,8 +221,7 @@ public static IEnumerable GetContainingKeys([NotNull] this IProperty prope /// /// Gets the scale of data that is allowed in this property. - /// For example, if the property is a - /// then this is the maximum number of decimal places. + /// For example, if the property is a then this is the maximum number of decimal places. /// /// The property to get the scale of. /// The scale, or if none is defined. diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index bc0d2879f53..5d093a98068 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -649,6 +649,17 @@ public virtual void Passes_for_compatible_duplicate_column_names_within_hierarch Validate(modelBuilder.Model); } + [ConditionalFact] + public virtual void Passes_for_shared_columns() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().Property(a => a.Id).HasMaxLength(20).HasPrecision(15, 10).IsUnicode(); + modelBuilder.Entity().OwnsOne(a => a.FavoritePerson); + modelBuilder.Entity(); + + Validate(modelBuilder.Model); + } + [ConditionalFact] public virtual void Detects_duplicate_foreignKey_names_within_hierarchy_with_different_principal_tables() {