diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs index debfc0ca146..f0e7432747b 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs @@ -148,6 +148,20 @@ protected virtual void Generate([NotNull] AddColumnOperation operation, [NotNull .Append(Code.Literal(operation.MaxLength.Value)); } + if (operation.Precision.HasValue) + { + builder.AppendLine(",") + .Append("precision: ") + .Append(Code.Literal(operation.Precision.Value)); + } + + if (operation.Scale.HasValue) + { + builder.AppendLine(",") + .Append("scale: ") + .Append(Code.Literal(operation.Scale.Value)); + } + if (operation.IsRowVersion) { builder @@ -492,6 +506,20 @@ protected virtual void Generate([NotNull] AlterColumnOperation operation, [NotNu .Append(Code.Literal(operation.MaxLength.Value)); } + if (operation.Precision.HasValue) + { + builder.AppendLine(",") + .Append("precision: ") + .Append(Code.Literal(operation.Precision.Value)); + } + + if (operation.Scale.HasValue) + { + builder.AppendLine(",") + .Append("scale: ") + .Append(Code.Literal(operation.Scale.Value)); + } + if (operation.IsRowVersion) { builder @@ -569,6 +597,20 @@ protected virtual void Generate([NotNull] AlterColumnOperation operation, [NotNu .Append(Code.Literal(operation.OldColumn.MaxLength.Value)); } + if (operation.OldColumn.Precision.HasValue) + { + builder.AppendLine(",") + .Append("oldPrecision: ") + .Append(Code.Literal(operation.OldColumn.Precision.Value)); + } + + if (operation.OldColumn.Scale.HasValue) + { + builder.AppendLine(",") + .Append("oldScale: ") + .Append(Code.Literal(operation.OldColumn.Scale.Value)); + } + if (operation.OldColumn.IsRowVersion) { builder @@ -1033,6 +1075,22 @@ protected virtual void Generate([NotNull] CreateTableOperation operation, [NotNu .Append(", "); } + if (column.Precision.HasValue) + { + builder + .Append("precision: ") + .Append(Code.Literal(column.Precision.Value)) + .Append(", "); + } + + if (column.Scale.HasValue) + { + builder + .Append("scale: ") + .Append(Code.Literal(column.Scale.Value)) + .Append(", "); + } + if (column.IsRowVersion) { builder.Append("rowVersion: true, "); diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index e6a44b3ae98..5fd4fad80cb 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -576,6 +576,10 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property, nameof(PropertyBuilder.HasMaxLength), stringBuilder); + GenerateFluentApiForPrecisionAndScale( + ref annotations, + stringBuilder); + GenerateFluentApiForAnnotation( ref annotations, CoreAnnotationNames.Unicode, @@ -1300,6 +1304,50 @@ protected virtual void GenerateFluentApiForAnnotation( } } + /// + /// Generates a Fluent API call for the Precision and Scale annotations. + /// + /// The list of annotations. + /// The builder code is added to. + protected virtual void GenerateFluentApiForPrecisionAndScale( + [NotNull] ref List annotations, + [NotNull] IndentedStringBuilder stringBuilder) + { + var precisionAnnotation = annotations + .FirstOrDefault(a => a.Name == CoreAnnotationNames.Precision); + var precisionValue = precisionAnnotation?.Value; + + if (precisionValue != null) + { + stringBuilder + .AppendLine() + .Append(".") + .Append(nameof(PropertyBuilder.HasPrecision)) + .Append("(") + .Append(Code.UnknownLiteral(precisionValue)); + + var scaleAnnotation = annotations + .FirstOrDefault(a => a.Name == CoreAnnotationNames.Scale); + var scaleValue = (int?)scaleAnnotation?.Value; + + if (scaleValue != null) + { + if (scaleValue != 0) + { + stringBuilder + .Append(", ") + .Append(Code.UnknownLiteral(scaleValue)); + } + + annotations.Remove(scaleAnnotation); + } + + stringBuilder.Append(")"); + + annotations.Remove(precisionAnnotation); + } + } + /// /// Generates code for an annotation. /// diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 12341702c57..c3806d3088c 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -633,6 +633,8 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) var annotations = property.GetAnnotations().ToList(); RemoveAnnotation(ref annotations, CoreAnnotationNames.MaxLength); + RemoveAnnotation(ref annotations, CoreAnnotationNames.Precision); + RemoveAnnotation(ref annotations, CoreAnnotationNames.Scale); RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping); RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode); RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName); @@ -695,6 +697,21 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) } } + var precision = property.GetPrecision(); + var scale = property.GetScale(); + if (precision != null && scale != null && scale != 0) + { + lines.Add( + $".{nameof(PropertyBuilder.HasPrecision)}" + + $"({_code.Literal(precision.Value)}, {_code.Literal(scale.Value)})"); + } + else if (precision != null) + { + lines.Add( + $".{nameof(PropertyBuilder.HasPrecision)}" + + $"({_code.Literal(precision.Value)})"); + } + if (property.IsUnicode() != null) { lines.Add( diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 81fca09a0e6..766504dc43f 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -444,6 +444,20 @@ protected virtual PropertyBuilder VisitColumn([NotNull] EntityTypeBuilder builde property.HasMaxLength(typeScaffoldingInfo.ScaffoldMaxLength.Value); } + if (typeScaffoldingInfo.ScaffoldPrecision.HasValue) + { + if (typeScaffoldingInfo.ScaffoldScale.HasValue) + { + property.HasPrecision( + typeScaffoldingInfo.ScaffoldPrecision.Value, + typeScaffoldingInfo.ScaffoldScale.Value); + } + else + { + property.HasPrecision(typeScaffoldingInfo.ScaffoldPrecision.Value); + } + } + if (column.ValueGenerated == ValueGenerated.OnAdd) { property.ValueGeneratedOnAdd(); diff --git a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs index 3b2ba15e9d0..54fc9694210 100644 --- a/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs +++ b/src/EFCore.Design/Scaffolding/Internal/ScaffoldingTypeMapper.cs @@ -57,104 +57,98 @@ public ScaffoldingTypeMapper([NotNull] IRelationalTypeMappingSource typeMappingS bool? scaffoldUnicode = null; bool? scaffoldFixedLength = null; int? scaffoldMaxLength = null; - - if (mapping.ClrType == typeof(byte[])) + int? scaffoldPrecision = null; + int? scaffoldScale = null; + + var unwrappedClrType = mapping.ClrType.UnwrapNullableType(); + + // Check for inference + var defaultTypeMapping = _typeMappingSource.FindMapping( + unwrappedClrType, + null, + keyOrIndex, + unicode: mapping.IsUnicode, + size: mapping.Size, + rowVersion: rowVersion, + fixedLength: mapping.IsFixedLength, + precision: mapping.Precision, + scale: mapping.Scale); + + if (defaultTypeMapping != null + && string.Equals(defaultTypeMapping.StoreType, storeType, StringComparison.OrdinalIgnoreCase)) { - // Check for inference - var byteArrayMapping = _typeMappingSource.FindMapping( - typeof(byte[]), + canInfer = true; + + // Check for Unicode + var unicodeMapping = _typeMappingSource.FindMapping( + unwrappedClrType, null, keyOrIndex, + unicode: null, + size: mapping.Size, rowVersion: rowVersion, + fixedLength: mapping.IsFixedLength, + precision: mapping.Precision, + scale: mapping.Scale); + + scaffoldUnicode = unicodeMapping.IsUnicode != defaultTypeMapping.IsUnicode ? (bool?)defaultTypeMapping.IsUnicode : null; + + // Check for fixed-length + var fixedLengthMapping = _typeMappingSource.FindMapping( + unwrappedClrType, + null, + keyOrIndex, + unicode: mapping.IsUnicode, size: mapping.Size, - fixedLength: mapping.IsFixedLength); - - if (byteArrayMapping.StoreType.Equals(storeType, StringComparison.OrdinalIgnoreCase)) - { - canInfer = true; - - // Check for fixed-length - var fixedLengthMapping = _typeMappingSource.FindMapping( - typeof(byte[]), - null, - keyOrIndex, - rowVersion: rowVersion, - size: mapping.Size, - fixedLength: false); - - scaffoldFixedLength = fixedLengthMapping.IsFixedLength != byteArrayMapping.IsFixedLength - ? (bool?)byteArrayMapping.IsFixedLength - : null; - - // Check for size - var sizedMapping = _typeMappingSource.FindMapping( - typeof(byte[]), - null, - keyOrIndex, - rowVersion: rowVersion, - fixedLength: mapping.IsFixedLength); - - scaffoldMaxLength = sizedMapping.Size != byteArrayMapping.Size ? byteArrayMapping.Size : null; - } - } - else if (mapping.ClrType == typeof(string)) - { - // Check for inference - var stringMapping = _typeMappingSource.FindMapping( - typeof(string), + fixedLength: null, + precision: mapping.Precision, + scale: mapping.Scale); + + scaffoldFixedLength = fixedLengthMapping.IsFixedLength != defaultTypeMapping.IsFixedLength + ? (bool?)defaultTypeMapping.IsFixedLength + : null; + + // Check for size (= max-length) + var sizedMapping = _typeMappingSource.FindMapping( + unwrappedClrType, + null, + keyOrIndex, + unicode: mapping.IsUnicode, + size: null, + rowVersion: rowVersion, + fixedLength: false, // Fixed length with no size is not valid + precision: mapping.Precision, + scale: mapping.Scale); + + scaffoldMaxLength = sizedMapping.Size != defaultTypeMapping.Size ? defaultTypeMapping.Size : null; + + // Check for precision + var precisionMapping = _typeMappingSource.FindMapping( + unwrappedClrType, null, keyOrIndex, unicode: mapping.IsUnicode, size: mapping.Size, - fixedLength: mapping.IsFixedLength); - - if (stringMapping.StoreType.Equals(storeType, StringComparison.OrdinalIgnoreCase)) - { - canInfer = true; - - // Check for Unicode - var unicodeMapping = _typeMappingSource.FindMapping( - typeof(string), - null, - keyOrIndex, - unicode: true, - size: mapping.Size, - fixedLength: mapping.IsFixedLength); - - scaffoldUnicode = unicodeMapping.IsUnicode != stringMapping.IsUnicode ? (bool?)stringMapping.IsUnicode : null; - - // Check for fixed-length - var fixedLengthMapping = _typeMappingSource.FindMapping( - typeof(string), - null, - keyOrIndex, - unicode: mapping.IsUnicode, - size: mapping.Size, - fixedLength: false); - - scaffoldFixedLength = fixedLengthMapping.IsFixedLength != stringMapping.IsFixedLength - ? (bool?)stringMapping.IsFixedLength - : null; - - var sizedMapping = _typeMappingSource.FindMapping( - typeof(string), - null, - keyOrIndex, - unicode: mapping.IsUnicode, - fixedLength: false); // Fixed length with no size is not valid - - scaffoldMaxLength = sizedMapping.Size != stringMapping.Size ? stringMapping.Size : null; - } - } - else - { - var defaultMapping = _typeMappingSource.FindMapping(mapping.ClrType); + rowVersion: rowVersion, + fixedLength: mapping.IsFixedLength, + precision: null, + scale: mapping.Scale); + + scaffoldPrecision = precisionMapping.Precision != defaultTypeMapping.Precision ? defaultTypeMapping.Precision : null; + + // Check for scale + var scaleMapping = _typeMappingSource.FindMapping( + unwrappedClrType, + null, + keyOrIndex, + unicode: mapping.IsUnicode, + size: mapping.Size, + rowVersion: rowVersion, + fixedLength: mapping.IsFixedLength, + precision: mapping.Precision, + scale: null); - if (string.Equals(defaultMapping?.StoreType, storeType, StringComparison.OrdinalIgnoreCase) - && mapping.ClrType.UnwrapNullableType() != typeof(decimal)) - { - canInfer = true; - } + scaffoldScale = scaleMapping.Scale != defaultTypeMapping.Scale ? defaultTypeMapping.Scale : null; } return new TypeScaffoldingInfo( @@ -162,7 +156,9 @@ public ScaffoldingTypeMapper([NotNull] IRelationalTypeMappingSource typeMappingS canInfer, scaffoldUnicode, scaffoldMaxLength, - scaffoldFixedLength); + scaffoldFixedLength, + scaffoldPrecision, + scaffoldScale); } } } diff --git a/src/EFCore.Design/Scaffolding/Internal/TypeScaffoldingInfo.cs b/src/EFCore.Design/Scaffolding/Internal/TypeScaffoldingInfo.cs index 6baa1219495..0bcad593be1 100644 --- a/src/EFCore.Design/Scaffolding/Internal/TypeScaffoldingInfo.cs +++ b/src/EFCore.Design/Scaffolding/Internal/TypeScaffoldingInfo.cs @@ -28,13 +28,17 @@ public TypeScaffoldingInfo( bool inferred, bool? scaffoldUnicode, int? scaffoldMaxLength, - bool? scaffoldFixedLength) + bool? scaffoldFixedLength, + int? scaffoldPrecision, + int? scaffoldScale) { Check.NotNull(clrType, nameof(clrType)); IsInferred = inferred; ScaffoldUnicode = scaffoldUnicode; ScaffoldMaxLength = scaffoldMaxLength; + ScaffoldPrecision = scaffoldPrecision; + ScaffoldScale = scaffoldScale; ScaffoldFixedLength = scaffoldFixedLength; ClrType = clrType; } @@ -78,5 +82,21 @@ public TypeScaffoldingInfo( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual int? ScaffoldMaxLength { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? ScaffoldPrecision { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? ScaffoldScale { get; } } } diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index 68cbab91468..9c93f86ec6e 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -27,6 +27,18 @@ public interface IColumn : IColumnBase /// int? MaxLength => PropertyMappings.First().Property.GetMaxLength(); + /// + /// 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(); + + /// + /// 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(); + /// /// Gets a value indicating whether or not the property can persist Unicode characters. /// diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 95e0928272a..168c0c57885 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -869,6 +869,8 @@ private bool PropertyStructureEquals(IProperty source, IProperty target) && source.IsConcurrencyToken == target.IsConcurrencyToken && source.ValueGenerated == target.ValueGenerated && source.GetMaxLength() == target.GetMaxLength() + && source.GetPrecision() == target.GetPrecision() + && source.GetScale() == target.GetScale() && source.IsColumnNullable() == target.IsColumnNullable() && source.IsUnicode() == target.IsUnicode() && source.IsFixedLength() == target.IsFixedLength() @@ -1066,6 +1068,8 @@ private void Initialize( columnOperation.ColumnType = column.Type; columnOperation.MaxLength = column.MaxLength; + columnOperation.Precision = column.Precision; + columnOperation.Scale = column.Scale; columnOperation.IsUnicode = column.IsUnicode; columnOperation.IsFixedLength = column.IsFixedLength; columnOperation.IsRowVersion = column.IsRowVersion; diff --git a/src/EFCore.Relational/Migrations/MigrationBuilder.cs b/src/EFCore.Relational/Migrations/MigrationBuilder.cs index 4da394f0eeb..7b3238fadc0 100644 --- a/src/EFCore.Relational/Migrations/MigrationBuilder.cs +++ b/src/EFCore.Relational/Migrations/MigrationBuilder.cs @@ -61,6 +61,12 @@ public MigrationBuilder([CanBeNull] string activeProvider) /// The SQL expression to use to compute the column value. /// Indicates whether or not the column is constrained to fixed-length data. /// A comment to associate with the column. + /// + /// The maximum number of digits that is allowed in this column, or null if not specified or not applicable. + /// + /// + /// The maximum number of decimal places that is allowed in this column, or null if not specified or not applicable. + /// /// A builder to allow annotations to be added to the operation. public virtual OperationBuilder AddColumn( [NotNull] string name, @@ -75,7 +81,9 @@ public virtual OperationBuilder AddColumn( [CanBeNull] string defaultValueSql = null, [CanBeNull] string computedColumnSql = null, bool? fixedLength = null, - [CanBeNull] string comment = null) + [CanBeNull] string comment = null, + int? precision = null, + int? scale = null) { Check.NotEmpty(name, nameof(name)); Check.NotEmpty(table, nameof(table)); @@ -95,7 +103,9 @@ public virtual OperationBuilder AddColumn( DefaultValueSql = defaultValueSql, ComputedColumnSql = computedColumnSql, IsFixedLength = fixedLength, - Comment = comment + Comment = comment, + Precision = precision, + Scale = scale, }; Operations.Add(operation); @@ -346,6 +356,18 @@ public virtual OperationBuilder AddUniqueConstrain /// Indicates whether or not the column was previously constrained to fixed-length data. /// A comment to associate with the column. /// The previous comment to associate with the column. + /// + /// The maximum number of digits that is allowed in this column, or null if not specified or not applicable. + /// + /// + /// The previous maximum number of digits that is allowed in this column, or null if not specified or not applicable. + /// + /// + /// The maximum number of decimal places that is allowed in this column, or null if not specified or not applicable. + /// + /// + /// The previous maximum number of decimal places that is allowed in this column, or null if not specified or not applicable. + /// /// A builder to allow annotations to be added to the operation. public virtual AlterOperationBuilder AlterColumn( [NotNull] string name, @@ -371,7 +393,11 @@ public virtual AlterOperationBuilder AlterColumn( bool? fixedLength = null, bool? oldFixedLength = null, [CanBeNull] string comment = null, - [CanBeNull] string oldComment = null) + [CanBeNull] string oldComment = null, + int? precision = null, + int? oldPrecision = null, + int? scale = null, + int? oldScale = null) { Check.NotEmpty(name, nameof(name)); Check.NotEmpty(table, nameof(table)); @@ -392,6 +418,8 @@ public virtual AlterOperationBuilder AlterColumn( ComputedColumnSql = computedColumnSql, IsFixedLength = fixedLength, Comment = comment, + Precision = precision, + Scale = scale, OldColumn = new ColumnOperation { ClrType = oldClrType ?? typeof(T), @@ -404,7 +432,9 @@ public virtual AlterOperationBuilder AlterColumn( DefaultValueSql = oldDefaultValueSql, ComputedColumnSql = oldComputedColumnSql, IsFixedLength = oldFixedLength, - Comment = oldComment + Comment = oldComment, + Precision = oldPrecision, + Scale = oldScale } }; diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs index 8fce8d9985b..d4cf5caea70 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs @@ -1201,6 +1201,8 @@ protected virtual string GetColumnType( { if (operation.IsUnicode == column.IsUnicode && operation.MaxLength == column.MaxLength + && operation.Precision == column.Precision + && operation.Scale == column.Scale && operation.IsFixedLength == column.IsFixedLength && operation.IsRowVersion == column.IsRowVersion) { @@ -1219,7 +1221,9 @@ protected virtual string GetColumnType( operation.IsUnicode, operation.MaxLength, operation.IsRowVersion, - operation.IsFixedLength) + operation.IsFixedLength, + operation.Precision, + operation.Scale) .StoreType; } diff --git a/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs b/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs index c0142ab04a4..a0a9f2ca42f 100644 --- a/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs +++ b/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs @@ -45,6 +45,8 @@ public ColumnsBuilder([NotNull] CreateTableOperation createTableOperation) /// The SQL expression to use to compute the column value. /// Indicates whether or not the column is constrained to fixed-length data. /// A comment to be applied to the table. + /// The maximum number of digits for data in the column. + /// The maximum number of decimal places for data in the column. /// The same builder so that multiple calls can be chained. public virtual OperationBuilder Column( [CanBeNull] string type = null, @@ -57,7 +59,9 @@ public virtual OperationBuilder Column( [CanBeNull] string defaultValueSql = null, [CanBeNull] string computedColumnSql = null, bool? fixedLength = null, - [CanBeNull] string comment = null) + [CanBeNull] string comment = null, + int? precision = null, + int? scale = null) { var operation = new AddColumnOperation { @@ -74,7 +78,9 @@ public virtual OperationBuilder Column( DefaultValueSql = defaultValueSql, ComputedColumnSql = computedColumnSql, IsFixedLength = fixedLength, - Comment = comment + Comment = comment, + Precision = precision, + Scale = scale }; _createTableOperation.Columns.Add(operation); diff --git a/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs b/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs index 67e55b1cf0d..e9b27e4d8ad 100644 --- a/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs @@ -39,6 +39,18 @@ public class ColumnOperation : MigrationOperation /// public virtual int? MaxLength { get; set; } + /// + /// The maximum number of digits that the column can store, or null + /// if this is not specified or does not apply to this column type. + /// + public virtual int? Precision { get; set; } + + /// + /// The maximum number of decimal places that the column can store, or null + /// if this is not specified or does not apply to this column type. + /// + public virtual int? Scale { get; set; } + /// /// Indicates whether or not this column acts as an automatic concurrency token in the same vein /// as 'rowversion'/'timestamp' columns on SQL Server. diff --git a/src/EFCore.Relational/Storage/DecimalTypeMapping.cs b/src/EFCore.Relational/Storage/DecimalTypeMapping.cs index 0ddead7ed5d..d334f0a8bb7 100644 --- a/src/EFCore.Relational/Storage/DecimalTypeMapping.cs +++ b/src/EFCore.Relational/Storage/DecimalTypeMapping.cs @@ -24,10 +24,14 @@ public class DecimalTypeMapping : RelationalTypeMapping /// /// The name of the database type. /// The to be used. + /// The precision of data the property is configured to store, or null if the default precision is required. + /// The scale of data the property is configured to store, or null if the default scale is required. public DecimalTypeMapping( [NotNull] string storeType, - DbType? dbType = null) - : base(storeType, typeof(decimal), dbType) + DbType? dbType = null, + int? precision = null, + int? scale = null) + : base(storeType, typeof(decimal), dbType, precision: precision, scale: scale) { } diff --git a/src/EFCore.Relational/Storage/Internal/DbParameterCollectionExtensions.cs b/src/EFCore.Relational/Storage/Internal/DbParameterCollectionExtensions.cs index 5ec9b0d198b..f84009a8f1a 100644 --- a/src/EFCore.Relational/Storage/Internal/DbParameterCollectionExtensions.cs +++ b/src/EFCore.Relational/Storage/Internal/DbParameterCollectionExtensions.cs @@ -150,7 +150,7 @@ private static void FormatParameterValue(StringBuilder builder, object parameter { builder .Append('\'') - .Append(((DateTime)parameterValue).ToString("s")) + .Append(((DateTime)parameterValue).ToString("o")) .Append('\''); } else if (parameterValue.GetType() == typeof(DateTimeOffset)) diff --git a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs index 763759ac5c6..9a7236a388e 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs @@ -315,15 +315,21 @@ private static string GetBaseName(string storeType) /// The to be used. /// A value indicating whether the type should handle Unicode data or not. /// The size of data the property is configured to store, or null if no size is configured. + /// A value indicating whether the type has fixed length data or not. + /// The precision of data the property is configured to store, or null if no precision is configured. + /// The scale of data the property is configured to store, or null if no scale is configured. protected RelationalTypeMapping( [NotNull] string storeType, [NotNull] Type clrType, DbType? dbType = null, bool unicode = false, - int? size = null) + int? size = null, + bool fixedLength = false, + int? precision = null, + int? scale = null) : this( new RelationalTypeMappingParameters( - new CoreTypeMappingParameters(clrType), storeType, StoreTypePostfix.None, dbType, unicode, size)) + new CoreTypeMappingParameters(clrType), storeType, StoreTypePostfix.None, dbType, unicode, size, fixedLength, precision, scale)) { } @@ -404,6 +410,16 @@ public virtual RelationalTypeMapping Clone(in RelationalTypeMappingInfo mappingI /// public virtual int? Size => Parameters.Size; + /// + /// Gets the precision of data the property is configured to store, or null if no precision is configured. + /// + public virtual int? Precision => Parameters.Precision; + + /// + /// Gets the scale of data the property is configured to store, or null if no scale is configured. + /// + public virtual int? Scale => Parameters.Scale; + /// /// Gets a value indicating whether the type is constrained to fixed-length data. /// diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index dd557517062..bf209c4014a 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -415,6 +414,7 @@ protected virtual string ParseStoreTypeName( var openParen = storeTypeName.IndexOf("(", StringComparison.Ordinal); if (openParen > 0) { + string storeTypeNameBase = storeTypeName.Substring(0, openParen).Trim(); var closeParen = storeTypeName.IndexOf(")", openParen + 1, StringComparison.Ordinal); if (closeParen > openParen) { @@ -435,16 +435,35 @@ protected virtual string ParseStoreTypeName( else if (int.TryParse( storeTypeName.Substring(openParen + 1, closeParen - openParen - 1).Trim(), out var parsedSize)) { - size = parsedSize; - precision = parsedSize; + if (StoreTypeNameBaseUsesPrecision(storeTypeNameBase)) + { + precision = parsedSize; + scale = 0; + } + else + { + size = parsedSize; + } } - return storeTypeName.Substring(0, openParen).Trim(); + return storeTypeNameBase; } } } return storeTypeName; } + + /// + /// Returns whether the store type name base interprets + /// nameBase(n) as a precision rather than a length + /// + /// The name base of the store type + /// + /// true if the store type name base interprets nameBase(n) + /// as a precision rather than a length, false otherwise. + /// + protected virtual bool StoreTypeNameBaseUsesPrecision([NotNull] string storeTypeNameBase) + => false; } } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs index 174bbb22843..4f0a07a32e6 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeOffsetTypeMapping.cs @@ -16,7 +16,19 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal /// public class SqlServerDateTimeOffsetTypeMapping : DateTimeOffsetTypeMapping { - private const string DateTimeOffsetFormatConst = "{0:yyyy-MM-ddTHH:mm:ss.fffffffzzz}"; + // Note: this array will be accessed using the precision as an index + // so the order of the entries in this array is important + private readonly string[] _dateTimeOffsetFormats = + { + "'{0:yyyy-MM-ddTHH:mm:sszzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.fzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.ffzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.fffzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.ffffzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.fffffzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.ffffffzzz}'", + "'{0:yyyy-MM-ddTHH:mm:ss.fffffffzzz}'" + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -56,7 +68,23 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override string SqlLiteralFormatString => "'" + DateTimeOffsetFormatConst + "'"; + protected override string SqlLiteralFormatString + { + get + { + if (Precision.HasValue) + { + var precision = Precision.Value; + if (precision <= 7 + && precision >= 0) + { + return _dateTimeOffsetFormats[precision]; + } + } + + return _dateTimeOffsetFormats[7]; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -73,6 +101,11 @@ protected override void ConfigureParameter(DbParameter parameter) { parameter.Size = Size.Value; } + + if (Precision.HasValue) + { + parameter.Precision = unchecked((byte)Precision.Value); + } } } } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs index 32a41f1d254..cf8f171b50f 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDateTimeTypeMapping.cs @@ -21,6 +21,8 @@ public class SqlServerDateTimeTypeMapping : DateTimeTypeMapping private const string SmallDateTimeFormatConst = "'{0:yyyy-MM-ddTHH:mm:ss}'"; private const string DateTimeFormatConst = "'{0:yyyy-MM-ddTHH:mm:ss.fff}'"; + // Note: this array will be accessed using the precision as an index + // so the order of the entries in this array is important private readonly string[] _dateTime2Formats = { "'{0:yyyy-MM-ddTHH:mm:ss}'", @@ -78,6 +80,11 @@ protected override void ConfigureParameter(DbParameter parameter) { parameter.Size = Size.Value; } + + if (Precision.HasValue) + { + parameter.Precision = unchecked((byte)Precision.Value); + } } /// @@ -107,13 +114,13 @@ protected override string SqlLiteralFormatString case "smalldatetime": return SmallDateTimeFormatConst; default: - if (Size.HasValue) + if (Precision.HasValue) { - var size = Size.Value; - if (size <= 7 - && size >= 0) + var precision = Precision.Value; + if (precision <= 7 + && precision >= 0) { - return _dateTime2Formats[size]; + return _dateTime2Formats[precision]; } } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDecimalTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDecimalTypeMapping.cs index a11761176d6..3b600e51dd1 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDecimalTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDecimalTypeMapping.cs @@ -73,6 +73,16 @@ protected override void ConfigureParameter(DbParameter parameter) { parameter.Size = Size.Value; } + + if (Precision.HasValue) + { + parameter.Precision = unchecked((byte)Precision.Value); + } + + if (Scale.HasValue) + { + parameter.Scale = unchecked((byte)Scale.Value); + } } } } diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index db01c6a4cbe..5903bc8b8f4 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -359,5 +359,10 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn return null; } + + private static readonly List _nameBasesUsingPrecision = + new List() { "decimal", "dec", "numeric", "datetime2", "datetimeoffset" }; + protected override bool StoreTypeNameBaseUsesPrecision(string storeTypeNameBase) + => _nameBasesUsingPrecision.Contains(storeTypeNameBase); } } diff --git a/src/EFCore/Extensions/ConventionPropertyExtensions.cs b/src/EFCore/Extensions/ConventionPropertyExtensions.cs index 3cda66dcb59..04252efe63f 100644 --- a/src/EFCore/Extensions/ConventionPropertyExtensions.cs +++ b/src/EFCore/Extensions/ConventionPropertyExtensions.cs @@ -110,6 +110,46 @@ public static IEnumerable GetContainingKeys([NotNull] this IConv public static ConfigurationSource? GetMaxLengthConfigurationSource([NotNull] this IConventionProperty property) => property.FindAnnotation(CoreAnnotationNames.MaxLength)?.GetConfigurationSource(); + /// + /// Sets 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 to get the precision of. + /// The maximum number of digits that is allowed in this property. + /// Indicates whether the configuration was specified using a data annotation. + public static int? SetPrecision([NotNull] this IConventionProperty property, int? precision, bool fromDataAnnotation = false) + => property.AsProperty().SetPrecision( + precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// Returns the configuration source for . + /// + /// The property to find configuration source for. + /// The configuration source for . + public static ConfigurationSource? GetPrecisionConfigurationSource([NotNull] this IConventionProperty property) + => property.FindAnnotation(CoreAnnotationNames.Precision)?.GetConfigurationSource(); + + /// + /// Sets 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 to get the precision of. + /// The maximum number of decimal places that is allowed in this property. + /// Indicates whether the configuration was specified using a data annotation. + public static int? SetScale([NotNull] this IConventionProperty property, int? scale, bool fromDataAnnotation = false) + => property.AsProperty().SetScale( + scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// Returns the configuration source for . + /// + /// The property to find configuration source for. + /// The configuration source for . + public static ConfigurationSource? GetScaleConfigurationSource([NotNull] this IConventionProperty property) + => property.FindAnnotation(CoreAnnotationNames.Scale)?.GetConfigurationSource(); + /// /// Sets a value indicating whether this property can persist Unicode characters. /// diff --git a/src/EFCore/Extensions/MutablePropertyExtensions.cs b/src/EFCore/Extensions/MutablePropertyExtensions.cs index 8cedf558167..8e805676521 100644 --- a/src/EFCore/Extensions/MutablePropertyExtensions.cs +++ b/src/EFCore/Extensions/MutablePropertyExtensions.cs @@ -90,6 +90,26 @@ public static IEnumerable GetContainingKeys([NotNull] this IMutable public static void SetMaxLength([NotNull] this IMutableProperty property, int? maxLength) => property.AsProperty().SetMaxLength(maxLength, ConfigurationSource.Explicit); + /// + /// Sets 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 to set the precision of. + /// The maximum number of digits that is allowed in this property. + public static void SetPrecision([NotNull] this IMutableProperty property, int? precision) + => property.AsProperty().SetPrecision(precision, ConfigurationSource.Explicit); + + /// + /// Sets 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 to set the scale of. + /// The maximum number of decimal places that is allowed in this property. + public static void SetScale([NotNull] this IMutableProperty property, int? scale) + => property.AsProperty().SetScale(scale, ConfigurationSource.Explicit); + /// /// Sets a value indicating whether this property can persist Unicode characters. /// diff --git a/src/EFCore/Extensions/PropertyExtensions.cs b/src/EFCore/Extensions/PropertyExtensions.cs index a99b72a58fb..d075e25d529 100644 --- a/src/EFCore/Extensions/PropertyExtensions.cs +++ b/src/EFCore/Extensions/PropertyExtensions.cs @@ -223,6 +223,34 @@ public static IEnumerable GetContainingKeys([NotNull] this IProperty prope return (int?)property[CoreAnnotationNames.MaxLength]; } + /// + /// 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 to get the precision of. + /// The precision, or null if none if defined. + public static int? GetPrecision([NotNull] this IProperty property) + { + Check.NotNull(property, nameof(property)); + + return (int?)property[CoreAnnotationNames.Precision]; + } + + /// + /// 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 to get the scale of. + /// The scale, or null if none if defined. + public static int? GetScale([NotNull] this IProperty property) + { + Check.NotNull(property, nameof(property)); + + return (int?)property[CoreAnnotationNames.Scale]; + } + /// /// Gets a value indicating whether or not the property can persist Unicode characters. /// diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index c6f1353c3c8..9ea3f26745d 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -91,6 +91,38 @@ public virtual PropertyBuilder HasMaxLength(int maxLength) return this; } + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual PropertyBuilder HasPrecision(int precision, int scale) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + Builder.HasScale(scale, ConfigurationSource.Explicit); + + return this; + } + + /// + /// + /// Configures the precision of the property. + /// + /// + /// Note: has the side-effect of setting the scale to 0. + /// + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual PropertyBuilder HasPrecision(int precision) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + Builder.HasScale(0, ConfigurationSource.Explicit); + + return this; + } + /// /// Configures whether the property as capable of persisting unicode characters. /// Can only be set on properties. diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index cb288e46dbe..c9823806574 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -22,6 +22,22 @@ public static class CoreAnnotationNames /// public const string MaxLength = "MaxLength"; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string Precision = "Precision"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string Scale = "Scale"; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -256,6 +272,8 @@ public static class CoreAnnotationNames public static readonly ISet AllNames = new HashSet { MaxLength, + Precision, + Scale, Unicode, ProductVersion, ValueGeneratorFactory, diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index cf50a3c7658..726a8303f87 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -225,6 +225,62 @@ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configu => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) || Metadata.GetMaxLength() == maxLength; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder HasPrecision(int? precision, ConfigurationSource configurationSource) + { + if (CanSetPrecision(precision, configurationSource)) + { + Metadata.SetPrecision(precision, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetPrecisionConfigurationSource()) + || Metadata.GetPrecision() == precision; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder HasScale(int? scale, ConfigurationSource configurationSource) + { + if (CanSetScale(scale, configurationSource)) + { + Metadata.SetScale(scale, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetScaleConfigurationSource()) + || Metadata.GetScale() == scale; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 7d2bc8def0c..d0947fe6eaf 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -305,6 +305,42 @@ public virtual bool IsConcurrencyToken return unicode; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetPrecision(int? precision, ConfigurationSource configurationSource) + { + if (precision != null && precision < 0) + { + throw new ArgumentOutOfRangeException(nameof(precision)); + } + + this.SetOrRemoveAnnotation(CoreAnnotationNames.Precision, precision, configurationSource); + + return precision; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int? SetScale(int? scale, ConfigurationSource configurationSource) + { + if (scale != null && scale < 0) + { + throw new ArgumentOutOfRangeException(nameof(scale)); + } + + this.SetOrRemoveAnnotation(CoreAnnotationNames.Scale, scale, configurationSource); + + return scale; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index ed29b937720..02993a66406 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -57,6 +57,8 @@ public TypeMappingInfo( ValueConverter customConverter = null; int? size = null; + int? precision = null; + int? scale = null; bool? isUnicode = null; for (var i = 0; i < principals.Count; i++) { @@ -79,6 +81,24 @@ public TypeMappingInfo( } } + if (precision == null) + { + var precisionFromProperty = principal.GetPrecision(); + if (precisionFromProperty != null) + { + precision = precisionFromProperty; + } + } + + if (scale == null) + { + var scaleFromProperty = principal.GetScale(); + if (scaleFromProperty != null) + { + scale = scaleFromProperty; + } + } + if (isUnicode == null) { var unicode = principal.IsUnicode(); @@ -97,8 +117,8 @@ public TypeMappingInfo( IsUnicode = isUnicode ?? mappingHints?.IsUnicode ?? fallbackUnicode; IsRowVersion = property.IsConcurrencyToken && property.ValueGenerated == ValueGenerated.OnAddOrUpdate; ClrType = (customConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); - Scale = mappingHints?.Scale ?? fallbackScale; - Precision = mappingHints?.Precision ?? fallbackPrecision; + Scale = scale ?? mappingHints?.Scale ?? fallbackScale; + Precision = precision ?? mappingHints?.Precision ?? fallbackPrecision; } /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs index a9f2a9fa1c1..2a66cd275b6 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs @@ -76,6 +76,8 @@ public void AddColumnOperation_all_args() ColumnType = "int", IsUnicode = false, MaxLength = 30, + Precision = 10, + Scale = 5, IsRowVersion = true, IsNullable = true, DefaultValue = 1, @@ -98,6 +100,10 @@ public void AddColumnOperation_all_args() + _eol + " maxLength: 30," + _eol + + " precision: 10," + + _eol + + " scale: 5," + + _eol + " rowVersion: true," + _eol + " nullable: true," @@ -517,6 +523,8 @@ public void AlterColumnOperation_required_args() Assert.Null(o.IsUnicode); Assert.Null(o.IsFixedLength); Assert.Null(o.MaxLength); + Assert.Null(o.Precision); + Assert.Null(o.Scale); Assert.False(o.IsRowVersion); Assert.False(o.IsNullable); Assert.Null(o.DefaultValue); @@ -527,6 +535,8 @@ public void AlterColumnOperation_required_args() Assert.Null(o.OldColumn.IsUnicode); Assert.Null(o.OldColumn.IsFixedLength); Assert.Null(o.OldColumn.MaxLength); + Assert.Null(o.OldColumn.Precision); + Assert.Null(o.OldColumn.Scale); Assert.False(o.OldColumn.IsRowVersion); Assert.False(o.OldColumn.IsNullable); Assert.Null(o.OldColumn.DefaultValue); @@ -548,6 +558,8 @@ public void AlterColumnOperation_all_args() ColumnType = "int", IsUnicode = false, MaxLength = 30, + Precision = 10, + Scale = 5, IsRowVersion = true, IsNullable = true, DefaultValue = 1, @@ -559,6 +571,8 @@ public void AlterColumnOperation_all_args() ColumnType = "string", IsUnicode = false, MaxLength = 20, + Precision = 5, + Scale = 1, IsRowVersion = true, IsNullable = true, DefaultValue = 0, @@ -582,6 +596,10 @@ public void AlterColumnOperation_all_args() + _eol + " maxLength: 30," + _eol + + " precision: 10," + + _eol + + " scale: 5," + + _eol + " rowVersion: true," + _eol + " nullable: true," @@ -600,6 +618,10 @@ public void AlterColumnOperation_all_args() + _eol + " oldMaxLength: 20," + _eol + + " oldPrecision: 5," + + _eol + + " oldScale: 1," + + _eol + " oldRowVersion: true," + _eol + " oldNullable: true," @@ -617,6 +639,8 @@ public void AlterColumnOperation_all_args() Assert.False(o.IsUnicode); Assert.True(o.IsFixedLength); Assert.Equal(30, o.MaxLength); + Assert.Equal(10, o.Precision); + Assert.Equal(5, o.Scale); Assert.True(o.IsRowVersion); Assert.True(o.IsNullable); Assert.Equal(1, o.DefaultValue); @@ -628,6 +652,8 @@ public void AlterColumnOperation_all_args() Assert.False(o.OldColumn.IsUnicode); Assert.True(o.OldColumn.IsFixedLength); Assert.Equal(20, o.OldColumn.MaxLength); + Assert.Equal(5, o.OldColumn.Precision); + Assert.Equal(1, o.OldColumn.Scale); Assert.True(o.OldColumn.IsRowVersion); Assert.True(o.OldColumn.IsNullable); Assert.Equal(0, o.OldColumn.DefaultValue); @@ -723,6 +749,8 @@ public void AlterColumnOperation_computedColumnSql() Assert.Null(o.OldColumn.IsUnicode); Assert.Null(o.OldColumn.IsFixedLength); Assert.Null(o.OldColumn.MaxLength); + Assert.Null(o.OldColumn.Precision); + Assert.Null(o.OldColumn.Scale); Assert.False(o.OldColumn.IsRowVersion); Assert.False(o.OldColumn.IsNullable); Assert.Null(o.OldColumn.DefaultValue); @@ -1134,6 +1162,8 @@ public void CreateTableOperation_Columns_all_args() IsUnicode = false, IsFixedLength = true, MaxLength = 30, + Precision = 20, + Scale = 10, IsRowVersion = true, IsNullable = true, DefaultValue = 1 @@ -1150,7 +1180,7 @@ public void CreateTableOperation_Columns_all_args() + _eol + " {" + _eol - + " PostId = table.Column(name: \"Post Id\", type: \"int\", unicode: false, fixedLength: true, maxLength: 30, rowVersion: true, nullable: true, defaultValue: 1)" + + " PostId = table.Column(name: \"Post Id\", type: \"int\", unicode: false, fixedLength: true, maxLength: 30, precision: 20, scale: 10, rowVersion: true, nullable: true, defaultValue: 1)" + _eol + " }," + _eol diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index f94aedd94ec..7083dc8e5d0 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -43,6 +43,8 @@ public void Test_new_annotations_handled_for_entity_types() var notForEntityType = new HashSet { CoreAnnotationNames.MaxLength, + CoreAnnotationNames.Precision, + CoreAnnotationNames.Scale, CoreAnnotationNames.Unicode, CoreAnnotationNames.ProductVersion, CoreAnnotationNames.ValueGeneratorFactory, @@ -169,6 +171,7 @@ public void Test_new_annotations_handled_for_properties() var forProperty = new Dictionary { { CoreAnnotationNames.MaxLength, (256, $@"{columnMapping}{_nl}.{nameof(PropertyBuilder.HasMaxLength)}(256)") }, + { CoreAnnotationNames.Precision, (4, $@"{columnMapping}{_nl}.{nameof(PropertyBuilder.HasPrecision)}(4)") }, { CoreAnnotationNames.Unicode, (false, $@"{columnMapping}{_nl}.{nameof(PropertyBuilder.IsUnicode)}(false)") }, { CoreAnnotationNames.ValueConverter, (new ValueConverter(v => v, v => (int)v), diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 5f72f73bd53..32cc80d21f9 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -264,6 +264,33 @@ public void ValueGenerated_works() }); } + [ConditionalFact] + public void HasPrecision_works() + { + Test( + modelBuilder => modelBuilder.Entity( + "Entity", + x => + { + x.Property("HasPrecision").HasPrecision(12); + x.Property("HasPrecisionAndScale").HasPrecision(14, 7); + }), + new ModelCodeGenerationOptions(), + code => + { + Assert.Contains("Property(e => e.HasPrecision).HasPrecision(12)", code.ContextFile.Code); + Assert.Contains("Property(e => e.HasPrecisionAndScale).HasPrecision(14, 7)", code.ContextFile.Code); + }, + model => + { + var entity = model.FindEntityType("TestNamespace.Entity"); + Assert.Equal(12, entity.GetProperty("HasPrecision").GetPrecision()); + Assert.Equal(0, entity.GetProperty("HasPrecision").GetScale()); + Assert.Equal(14, entity.GetProperty("HasPrecisionAndScale").GetPrecision()); + Assert.Equal(7, entity.GetProperty("HasPrecisionAndScale").GetScale()); + }); + } + private class TestCodeGeneratorPlugin : ProviderCodeGeneratorPlugin { public override MethodCallCodeFragment GenerateProviderOptions() diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingTypeMapperSqlServerTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingTypeMapperSqlServerTest.cs index 39cbdbc50b4..e6266371a2c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingTypeMapperSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingTypeMapperSqlServerTest.cs @@ -21,7 +21,7 @@ public void Maps_int_column(bool isKeyOrIndex) { var mapping = CreateMapper().FindMapping("int", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalTheory] @@ -31,17 +31,37 @@ public void Maps_bigint_column(bool isKeyOrIndex) { var mapping = CreateMapper().FindMapping("bigint", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalTheory] [InlineData(false)] [InlineData(true)] - public void Maps_decimal_column(bool isKeyOrIndex) + public void Maps_default_decimal_column(bool isKeyOrIndex) { - var mapping = CreateMapper().FindMapping("decimal(18, 2)", isKeyOrIndex, rowVersion: false); + var mapping = CreateMapper().FindMapping("decimal(18,2)", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public void Maps_non_default_decimal_column(bool isKeyOrIndex) + { + var mapping = CreateMapper().FindMapping("decimal(14,3)", isKeyOrIndex, rowVersion: false); + + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: 14, scale: 3); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public void Maps_numeric_column(bool isKeyOrIndex) + { + var mapping = CreateMapper().FindMapping("numeric(17,4)", isKeyOrIndex, rowVersion: false); + + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalTheory] @@ -51,7 +71,7 @@ public void Maps_bit_column(bool isKeyOrIndex) { var mapping = CreateMapper().FindMapping("bit", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalTheory] @@ -61,7 +81,7 @@ public void Maps_datetime_column(bool isKeyOrIndex) { var mapping = CreateMapper().FindMapping("datetime", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalTheory] @@ -71,7 +91,7 @@ public void Maps_datetime2_column(bool isKeyOrIndex) { var mapping = CreateMapper().FindMapping("datetime2", isKeyOrIndex, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -79,7 +99,7 @@ public void Maps_normal_varbinary_max_column() { var mapping = CreateMapper().FindMapping("varbinary(max)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -87,7 +107,7 @@ public void Maps_normal_varbinary_sized_column() { var mapping = CreateMapper().FindMapping("varbinary(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -95,7 +115,7 @@ public void Maps_normal_binary_sized_column() { var mapping = CreateMapper().FindMapping("binary(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -103,7 +123,7 @@ public void Maps_key_varbinary_max_column() { var mapping = CreateMapper().FindMapping("varbinary(max)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -111,7 +131,7 @@ public void Maps_key_varbinary_sized_column() { var mapping = CreateMapper().FindMapping("varbinary(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -119,7 +139,7 @@ public void Maps_key_varbinary_default_sized_column() { var mapping = CreateMapper().FindMapping("varbinary(900)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -127,7 +147,7 @@ public void Maps_key_binary_sized_column() { var mapping = CreateMapper().FindMapping("binary(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -135,7 +155,7 @@ public void Maps_key_binary_default_sized_column() { var mapping = CreateMapper().FindMapping("binary(900)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -143,7 +163,7 @@ public void Maps_rowversion_rowversion_column() { var mapping = CreateMapper().FindMapping("rowversion", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -151,7 +171,7 @@ public void Maps_rowversion_varbinary_max_column() { var mapping = CreateMapper().FindMapping("varbinary(max)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -159,7 +179,7 @@ public void Maps_rowversion_varbinary_sized_column() { var mapping = CreateMapper().FindMapping("varbinary(200)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -167,7 +187,7 @@ public void Maps_rowversion_varbinary_default_sized_column() { var mapping = CreateMapper().FindMapping("varbinary(8)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -175,7 +195,7 @@ public void Maps_rowversion_binary_max_column() { var mapping = CreateMapper().FindMapping("binary(max)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -183,7 +203,7 @@ public void Maps_rowversion_binary_sized_column() { var mapping = CreateMapper().FindMapping("binary(200)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -191,7 +211,7 @@ public void Maps_rowversion_binary_default_sized_column() { var mapping = CreateMapper().FindMapping("binary(8)", keyOrIndex: false, rowVersion: true); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -199,7 +219,7 @@ public void Maps_normal_nvarchar_max_column() { var mapping = CreateMapper().FindMapping("nvarchar(max)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -207,7 +227,7 @@ public void Maps_normal_nvarchar_sized_column() { var mapping = CreateMapper().FindMapping("nvarchar(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -215,7 +235,7 @@ public void Maps_normal_varchar_max_column() { var mapping = CreateMapper().FindMapping("varchar(max)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -223,7 +243,7 @@ public void Maps_normal_varchar_sized_column() { var mapping = CreateMapper().FindMapping("varchar(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -231,7 +251,7 @@ public void Maps_key_nvarchar_max_column() { var mapping = CreateMapper().FindMapping("nvarchar(max)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -239,7 +259,7 @@ public void Maps_key_nvarchar_sized_column() { var mapping = CreateMapper().FindMapping("nvarchar(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -247,7 +267,7 @@ public void Maps_key_varchar_max_column() { var mapping = CreateMapper().FindMapping("varchar(max)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -255,7 +275,7 @@ public void Maps_key_varchar_sized_column() { var mapping = CreateMapper().FindMapping("varchar(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -263,7 +283,7 @@ public void Maps_key_nvarchar_default_sized_column() { var mapping = CreateMapper().FindMapping("nvarchar(450)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -271,7 +291,7 @@ public void Maps_key_varchar_default_sized_column() { var mapping = CreateMapper().FindMapping("varchar(900)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: null); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -279,7 +299,7 @@ public void Maps_normal_nchar_sized_column() { var mapping = CreateMapper().FindMapping("nchar(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -287,7 +307,7 @@ public void Maps_normal_char_sized_column() { var mapping = CreateMapper().FindMapping("char(200)", keyOrIndex: false, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -295,7 +315,7 @@ public void Maps_key_nchar_max_column() { var mapping = CreateMapper().FindMapping("nchar(max)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -303,7 +323,7 @@ public void Maps_key_nchar_sized_column() { var mapping = CreateMapper().FindMapping("nchar(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -311,7 +331,7 @@ public void Maps_key_char_max_column() { var mapping = CreateMapper().FindMapping("char(max)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } [ConditionalFact] @@ -319,7 +339,7 @@ public void Maps_key_char_sized_column() { var mapping = CreateMapper().FindMapping("char(200)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: 200, unicode: false, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -327,7 +347,7 @@ public void Maps_key_nchar_default_sized_column() { var mapping = CreateMapper().FindMapping("nchar(450)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: null, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -335,7 +355,7 @@ public void Maps_key_char_default_sized_column() { var mapping = CreateMapper().FindMapping("char(900)", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: true); + AssertMapping(mapping, inferred: true, maxLength: null, unicode: false, fixedLength: true, precision: null, scale: null); } [ConditionalFact] @@ -343,16 +363,20 @@ public void Maps_text_column() { var mapping = CreateMapper().FindMapping("text", keyOrIndex: true, rowVersion: false); - AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null); + AssertMapping(mapping, inferred: false, maxLength: null, unicode: null, fixedLength: null, precision: null, scale: null); } - private static void AssertMapping(TypeScaffoldingInfo mapping, bool inferred, int? maxLength, bool? unicode, bool? fixedLength) + private static void AssertMapping( + TypeScaffoldingInfo mapping, bool inferred, int? maxLength, + bool? unicode, bool? fixedLength, int? precision, int? scale) { Assert.Same(typeof(T), mapping.ClrType); Assert.Equal(inferred, mapping.IsInferred); Assert.Equal(maxLength, mapping.ScaffoldMaxLength); Assert.Equal(unicode, mapping.ScaffoldUnicode); Assert.Equal(fixedLength, mapping.ScaffoldFixedLength); + Assert.Equal(precision, mapping.ScaffoldPrecision); + Assert.Equal(scale, mapping.ScaffoldScale); } private static ScaffoldingTypeMapper CreateMapper() diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 25d28b7688c..cdc9f5f7d8e 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -2057,6 +2057,70 @@ public void Alter_column_max_length() }); } + [ConditionalFact] + public void Alter_column_precision() + { + Execute( + source => source.Entity( + "Toad", + x => + { + x.Property("Id"); + x.Property("Salary"); + }), + target => target.Entity( + "Toad", + x => + { + x.Property("Id"); + x.Property("Salary") + .HasPrecision(10); + }), + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Equal("Toad", operation.Table); + Assert.Equal("Salary", operation.Name); + Assert.Equal(10, operation.Precision); + Assert.Equal(0, operation.Scale); + Assert.True(operation.IsDestructiveChange); + }); + } + + [ConditionalFact] + public void Alter_column_precision_and_scale() + { + Execute( + source => source.Entity( + "Toad", + x => + { + x.Property("Id"); + x.Property("Salary"); + }), + target => target.Entity( + "Toad", + x => + { + x.Property("Id"); + x.Property("Salary") + .HasPrecision(17, 5); + }), + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Equal("Toad", operation.Table); + Assert.Equal("Salary", operation.Name); + Assert.Equal(17, operation.Precision); + Assert.Equal(5, operation.Scale); + Assert.True(operation.IsDestructiveChange); + }); + } + [ConditionalFact] public void Alter_column_unicode() { diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs index bbd8cc43f5c..1cb744fe88c 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs @@ -94,6 +94,31 @@ public virtual void AddColumnOperation_with_maxLength_no_model() IsNullable = true }); + [ConditionalFact] + public virtual void AddColumnOperation_with_precision_and_scale_overridden() + => Generate( + modelBuilder => modelBuilder.Entity().Property("Pi").HasPrecision(30, 17), + new AddColumnOperation + { + Table = "Person", + Name = "Pi", + ClrType = typeof(decimal), + Precision = 15, + Scale = 10 + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_precision_and_scale_no_model() + => Generate( + new AddColumnOperation + { + Table = "Person", + Name = "Pi", + ClrType = typeof(decimal), + Precision = 20, + Scale = 7 + }); + [ConditionalFact] public virtual void AddForeignKeyOperation_without_principal_columns() => Generate( @@ -203,6 +228,7 @@ protected class Person { public int Id { get; set; } public string Name { get; set; } + public decimal Pi { get; set; } } } } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs index 7be1719f597..396717ed0e5 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs @@ -145,6 +145,39 @@ public void Key_with_store_type_is_picked_up_by_FK() GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Relationship1Id")).StoreType); } + [ConditionalFact] + public void Does_default_type_mapping_from_decimal() + { + var model = CreateModel(); + var mapper = CreateTestTypeMapper(); + + Assert.Equal( + "default_decimal_mapping", + GetMapping(mapper, model.FindEntityType(typeof(MyPrecisionType)).FindProperty("Id")).StoreType); + } + + [ConditionalFact] + public void Does_type_mapping_from_decimal_with_precision_only() + { + var model = CreateModel(); + var mapper = CreateTestTypeMapper(); + + Assert.Equal( + "decimal_mapping(16)", + GetMapping(mapper, model.FindEntityType(typeof(MyPrecisionType)).FindProperty("PrecisionOnly")).StoreType); + } + + [ConditionalFact] + public void Does_type_mapping_from_decimal_with_precision_and_scale() + { + var model = CreateModel(); + var mapper = CreateTestTypeMapper(); + + Assert.Equal( + "decimal_mapping(18,7)", + GetMapping(mapper, model.FindEntityType(typeof(MyPrecisionType)).FindProperty("PrecisionAndScale")).StoreType); + } + private static IRelationalTypeMappingSource CreateTestTypeMapper() => new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), @@ -214,7 +247,7 @@ public void Key_store_type_is_preferred_if_specified() GetMapping(mapper, model.FindEntityType(typeof(MyType)).FindProperty("Id")).StoreType); Assert.Equal( - "dec(6,1)", + "decimal_mapping(6,1)", GetMapping(mapper, model.FindEntityType(typeof(MyRelatedType1)).FindProperty("Relationship2Id")).StoreType); } diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs index 13827eaee7e..b0021aea033 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs @@ -22,6 +22,8 @@ protected IMutableModel CreateModel() builder.Entity().Property(e => e.Id).IsUnicode(false); builder.Entity().Property(e => e.Relationship2Id).HasMaxLength(767); builder.Entity().Property(e => e.Relationship2Id).IsUnicode(); + builder.Entity().Property(e => e.PrecisionOnly).HasPrecision(16); + builder.Entity().Property(e => e.PrecisionAndScale).HasPrecision(18, 7); return builder.Model; } @@ -33,6 +35,13 @@ protected class MyType public decimal Id { get; set; } } + protected class MyPrecisionType + { + public decimal Id { get; set; } + public decimal PrecisionOnly { get; set; } + public decimal PrecisionAndScale { get; set; } + } + protected class MyRelatedType1 { public string Id { get; set; } diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs index ae0e1bc7de4..db44bfe338c 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs @@ -196,6 +196,30 @@ protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInf size); } + if (clrType == typeof(decimal) + && !string.Equals("money", storeTypeName, StringComparison.Ordinal)) + { + var precision = mappingInfo.Precision; + var scale = mappingInfo.Scale; + if (precision == _defaultDecimalMapping.Precision + && scale == _defaultDecimalMapping.Scale) + { + return _defaultDecimalMapping; + } + + if (scale == null || scale == 0) + { + return new DecimalTypeMapping( + "decimal_mapping(" + precision + ")", + precision: precision); + } + + return new DecimalTypeMapping( + "decimal_mapping(" + precision + "," + scale + ")", + precision: precision, + scale: scale); + } + if (_simpleMappings.TryGetValue(clrType, out var mapping)) { return storeTypeName != null @@ -211,5 +235,8 @@ protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInf ? mappingFromName : null; } + + protected override bool StoreTypeNameBaseUsesPrecision(string storeTypeNameBase) + => "default_decimal_mapping" == storeTypeNameBase; } } diff --git a/test/EFCore.Relational.Tests/Utilities/DbParameterCollectionExtensionsTest.cs b/test/EFCore.Relational.Tests/Utilities/DbParameterCollectionExtensionsTest.cs index 783876adc54..12eccb3a9be 100644 --- a/test/EFCore.Relational.Tests/Utilities/DbParameterCollectionExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Utilities/DbParameterCollectionExtensionsTest.cs @@ -514,7 +514,7 @@ public void Formats_object_parameter_with_unusual_type() public void Formats_DateTime_parameter() { Assert.Equal( - "@param='1973-09-03T00:00:00'", + "@param='1973-09-03T00:00:00.0000000'", DbParameterCollectionExtensions.FormatParameter( "@param", new DateTime(1973, 9, 3), true, ParameterDirection.Input, DbType.DateTime2, false, 0, 0, 0)); } @@ -523,7 +523,7 @@ public void Formats_DateTime_parameter() public void Formats_DateTime_parameter_with_unusual_type() { Assert.Equal( - "@param='1973-09-03T00:00:00' (DbType = DateTime)", + "@param='1973-09-03T00:00:00.0000000' (DbType = DateTime)", DbParameterCollectionExtensions.FormatParameter( "@param", new DateTime(1973, 9, 3), true, ParameterDirection.Input, DbType.DateTime, false, 0, 0, 0)); } diff --git a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs index 51a920c0d80..09a7de7e2d0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs @@ -676,15 +676,15 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() @p12='D' (Nullable = false) (Size = 1) @p13='G' (Nullable = false) (Size = 1) (DbType = AnsiString) @p14='A' (Nullable = false) (Size = 1) (DbType = AnsiString) -@p15='2015-01-02T10:11:12' (DbType = Date) -@p16='2019-01-02T14:11:12' (DbType = DateTime) -@p17='2017-01-02T12:11:12' -@p18='2018-01-02T13:11:12' (DbType = DateTime) -@p19='2016-01-02T11:11:12.0000000+00:00' -@p20='101.1' -@p21='102.2' +@p15='2015-01-02T10:11:12.0000000' (DbType = Date) +@p16='2019-01-02T14:11:12.0000000' (DbType = DateTime) +@p17='2017-01-02T12:11:12.1234567' +@p18='2018-01-02T13:11:12.0000000' (DbType = DateTime) +@p19='2016-01-02T11:11:12.1234567+00:00' +@p20='101.1' (Precision = 18) (Scale = 2) +@p21='102.2' (Precision = 18) (Scale = 2) @p22='81.1' -@p23='103.3' +@p23='103.3' (Precision = 18) (Scale = 2) @p24='82.2' @p25='85.5' @p26='83.3' @@ -716,7 +716,7 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() @p48='4294967295' @p49='-1' @p50='-1' -@p51='18446744073709551615'", +@p51='18446744073709551615' (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -747,8 +747,8 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) Assert.Equal(84.4f, entity.FloatAsReal); Assert.Equal(85.5, entity.DoubleAsDoublePrecision); Assert.Equal(new DateTime(2015, 1, 2), entity.DateTimeAsDate); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime2); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(1234567), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(1234567), entity.DateTimeAsDatetime2); Assert.Equal(new DateTime(2018, 1, 2, 13, 11, 00), entity.DateTimeAsSmalldatetime); Assert.Equal(new DateTime(2019, 1, 2, 14, 11, 12), entity.DateTimeAsDatetime); Assert.Equal(new TimeSpan(11, 15, 12), entity.TimeSpanAsTime); @@ -804,8 +804,8 @@ private static MappedDataTypes CreateMappedDataTypes(int id) FloatAsReal = 84.4f, DoubleAsDoublePrecision = 85.5, DateTimeAsDate = new DateTime(2015, 1, 2, 10, 11, 12), - DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12), + DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(1234567), TimeSpan.Zero), + DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(1234567), DateTimeAsSmalldatetime = new DateTime(2018, 1, 2, 13, 11, 12), DateTimeAsDatetime = new DateTime(2019, 1, 2, 14, 11, 12), TimeSpanAsTime = new TimeSpan(11, 15, 12), @@ -870,15 +870,15 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types() @p12='D' (Size = 1) @p13='G' (Size = 1) (DbType = AnsiString) @p14='A' (Size = 1) (DbType = AnsiString) -@p15='2015-01-02T10:11:12' (Nullable = true) (DbType = Date) -@p16='2019-01-02T14:11:12' (Nullable = true) (DbType = DateTime) -@p17='2017-01-02T12:11:12' (Nullable = true) -@p18='2018-01-02T13:11:12' (Nullable = true) (DbType = DateTime) -@p19='2016-01-02T11:11:12.0000000+00:00' (Nullable = true) -@p20='101.1' (Nullable = true) -@p21='102.2' (Nullable = true) +@p15='2015-01-02T10:11:12.0000000' (Nullable = true) (DbType = Date) +@p16='2019-01-02T14:11:12.0000000' (Nullable = true) (DbType = DateTime) +@p17='2017-01-02T12:11:12.9876543' (Nullable = true) +@p18='2018-01-02T13:11:12.0000000' (Nullable = true) (DbType = DateTime) +@p19='2016-01-02T11:11:12.9876543+00:00' (Nullable = true) +@p20='101.1' (Nullable = true) (Precision = 18) (Scale = 2) +@p21='102.2' (Nullable = true) (Precision = 18) (Scale = 2) @p22='81.1' (Nullable = true) -@p23='103.3' (Nullable = true) +@p23='103.3' (Nullable = true) (Precision = 18) (Scale = 2) @p24='82.2' (Nullable = true) @p25='85.5' (Nullable = true) @p26='83.3' (Nullable = true) @@ -906,7 +906,7 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types() @p48='4294967295' (Nullable = true) @p49='-1' (Nullable = true) @p50='-1' (Nullable = true) -@p51='18446744073709551615' (Nullable = true)", +@p51='18446744073709551615' (Nullable = true) (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -933,8 +933,8 @@ private static void AssertMappedNullableDataTypes(MappedNullableDataTypes entity Assert.Equal(84.4f, entity.FloatAsReal); Assert.Equal(85.5, entity.DoubleAsDoublePrecision); Assert.Equal(new DateTime(2015, 1, 2), entity.DateTimeAsDate); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime2); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(9876543), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(9876543), entity.DateTimeAsDatetime2); Assert.Equal(new DateTime(2018, 1, 2, 13, 11, 00), entity.DateTimeAsSmalldatetime); Assert.Equal(new DateTime(2019, 1, 2, 14, 11, 12), entity.DateTimeAsDatetime); Assert.Equal(new TimeSpan(11, 15, 12), entity.TimeSpanAsTime); @@ -990,8 +990,8 @@ private static MappedNullableDataTypes CreateMappedNullableDataTypes(int id) FloatAsReal = 84.4f, DoubleAsDoublePrecision = 85.5, DateTimeAsDate = new DateTime(2015, 1, 2, 10, 11, 12), - DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12), + DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(9876543), TimeSpan.Zero), + DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(9876543), DateTimeAsSmalldatetime = new DateTime(2018, 1, 2, 13, 11, 12), DateTimeAsDatetime = new DateTime(2019, 1, 2, 14, 11, 12), TimeSpanAsTime = new TimeSpan(11, 15, 12), @@ -1061,10 +1061,10 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null() @p17=NULL (DbType = DateTime2) @p18=NULL (DbType = DateTime) @p19=NULL (DbType = DateTimeOffset) -@p20=NULL -@p21=NULL +@p20=NULL (Precision = 18) (Scale = 2) +@p21=NULL (Precision = 18) (Scale = 2) @p22=NULL -@p23=NULL +@p23=NULL (Precision = 18) (Scale = 2) @p24=NULL @p25=NULL @p26=NULL @@ -1092,7 +1092,7 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null() @p48=NULL (DbType = Int64) @p49=NULL (DbType = Int32) @p50=NULL (DbType = Int64) -@p51=NULL", +@p51=NULL (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -1326,11 +1326,11 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_scale() var parameters = DumpParameters(); Assert.Equal( @"@p0='77' -@p1='2017-01-02T12:11:12' (Size = 3) -@p2='2016-01-02T11:11:12.0000000+00:00' (Size = 3) -@p3='102.2' (Size = 3) -@p4='101.1' (Size = 3) -@p5='103.3' (Size = 3) +@p1='2017-01-02T12:11:12.3210000' (Precision = 3) +@p2='2016-01-02T11:11:12.7650000+00:00' (Precision = 3) +@p3='102' (Precision = 3) +@p4='101' (Precision = 3) +@p5='103' (Precision = 3) @p6='85.55000305175781' (Size = 25) @p7='85.5' (Size = 3) @p8='83.33000183105469' (Size = 25) @@ -1351,8 +1351,8 @@ private static void AssertMappedScaledDataTypes(MappedScaledDataTypes entity, in Assert.Equal(85.5f, entity.FloatAsDoublePrecision3); Assert.Equal(83.33f, entity.FloatAsFloat25); Assert.Equal(85.55f, entity.FloatAsDoublePrecision25); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset3); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime23); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12, 765), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset3); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12, 321), entity.DateTimeAsDatetime23); Assert.Equal(101m, entity.DecimalAsDecimal3); Assert.Equal(102m, entity.DecimalAsDec3); Assert.Equal(103m, entity.DecimalAsNumeric3); @@ -1366,11 +1366,11 @@ private static MappedScaledDataTypes CreateMappedScaledDataTypes(int id) FloatAsDoublePrecision3 = 85.5f, FloatAsFloat25 = 83.33f, FloatAsDoublePrecision25 = 85.55f, - DateTimeOffsetAsDatetimeoffset3 = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12), - DecimalAsDecimal3 = 101.1m, - DecimalAsDec3 = 102.2m, - DecimalAsNumeric3 = 103.3m + DateTimeOffsetAsDatetimeoffset3 = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12, 765), TimeSpan.Zero), + DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12, 321), + DecimalAsDecimal3 = 101m, + DecimalAsDec3 = 102m, + DecimalAsNumeric3 = 103m }; [ConditionalFact] @@ -1386,9 +1386,9 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_precisio var parameters = DumpParameters(); Assert.Equal( @"@p0='77' -@p1='102.2' -@p2='101.1' -@p3='103.3'", +@p1='102.2' (Precision = 5) (Scale = 2) +@p2='101.1' (Precision = 5) (Scale = 2) +@p3='103.3' (Precision = 5) (Scale = 2)", parameters, ignoreLineEndingDifferences: true); @@ -1441,15 +1441,15 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_identity @p11='D' (Nullable = false) (Size = 1) @p12='G' (Nullable = false) (Size = 1) (DbType = AnsiString) @p13='A' (Nullable = false) (Size = 1) (DbType = AnsiString) -@p14='2015-01-02T10:11:12' (DbType = Date) -@p15='2019-01-02T14:11:12' (DbType = DateTime) -@p16='2017-01-02T12:11:12' -@p17='2018-01-02T13:11:12' (DbType = DateTime) -@p18='2016-01-02T11:11:12.0000000+00:00' -@p19='101.1' -@p20='102.2' +@p14='2015-01-02T10:11:12.0000000' (DbType = Date) +@p15='2019-01-02T14:11:12.0000000' (DbType = DateTime) +@p16='2017-01-02T12:11:12.7654321' +@p17='2018-01-02T13:11:12.0000000' (DbType = DateTime) +@p18='2016-01-02T11:11:12.7654321+00:00' +@p19='101.1' (Precision = 18) (Scale = 2) +@p20='102.2' (Precision = 18) (Scale = 2) @p21='81.1' -@p22='103.3' +@p22='103.3' (Precision = 18) (Scale = 2) @p23='82.2' @p24='85.5' @p25='83.3' @@ -1478,7 +1478,7 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_identity @p48='4294967295' @p49='-1' @p50='-1' -@p51='18446744073709551615'", +@p51='18446744073709551615' (Precision = 20)", parameters, ignoreLineEndingDifferences: true); @@ -1505,8 +1505,8 @@ private static void AssertMappedDataTypesWithIdentity(MappedDataTypesWithIdentit Assert.Equal(84.4f, entity.FloatAsReal); Assert.Equal(85.5, entity.DoubleAsDoublePrecision); Assert.Equal(new DateTime(2015, 1, 2), entity.DateTimeAsDate); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime2); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(7654321), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(7654321), entity.DateTimeAsDatetime2); Assert.Equal(new DateTime(2018, 1, 2, 13, 11, 00), entity.DateTimeAsSmalldatetime); Assert.Equal(new DateTime(2019, 1, 2, 14, 11, 12), entity.DateTimeAsDatetime); Assert.Equal(new TimeSpan(11, 15, 12), entity.TimeSpanAsTime); @@ -1562,8 +1562,8 @@ private static MappedDataTypesWithIdentity CreateMappedDataTypesWithIdentity(int FloatAsReal = 84.4f, DoubleAsDoublePrecision = 85.5, DateTimeAsDate = new DateTime(2015, 1, 2, 10, 11, 12), - DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12), + DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(7654321), TimeSpan.Zero), + DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(7654321), DateTimeAsSmalldatetime = new DateTime(2018, 1, 2, 13, 11, 12), DateTimeAsDatetime = new DateTime(2019, 1, 2, 14, 11, 12), TimeSpanAsTime = new TimeSpan(11, 15, 12), @@ -1627,15 +1627,15 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types_with @p11='D' (Size = 1) @p12='G' (Size = 1) (DbType = AnsiString) @p13='A' (Size = 1) (DbType = AnsiString) -@p14='2015-01-02T10:11:12' (Nullable = true) (DbType = Date) -@p15='2019-01-02T14:11:12' (Nullable = true) (DbType = DateTime) -@p16='2017-01-02T12:11:12' (Nullable = true) -@p17='2018-01-02T13:11:12' (Nullable = true) (DbType = DateTime) -@p18='2016-01-02T11:11:12.0000000+00:00' (Nullable = true) -@p19='101.1' (Nullable = true) -@p20='102.2' (Nullable = true) +@p14='2015-01-02T10:11:12.0000000' (Nullable = true) (DbType = Date) +@p15='2019-01-02T14:11:12.0000000' (Nullable = true) (DbType = DateTime) +@p16='2017-01-02T12:11:12.2345678' (Nullable = true) +@p17='2018-01-02T13:11:12.0000000' (Nullable = true) (DbType = DateTime) +@p18='2016-01-02T11:11:12.2345678+00:00' (Nullable = true) +@p19='101.1' (Nullable = true) (Precision = 18) (Scale = 2) +@p20='102.2' (Nullable = true) (Precision = 18) (Scale = 2) @p21='81.1' (Nullable = true) -@p22='103.3' (Nullable = true) +@p22='103.3' (Nullable = true) (Precision = 18) (Scale = 2) @p23='82.2' (Nullable = true) @p24='85.5' (Nullable = true) @p25='83.3' (Nullable = true) @@ -1663,7 +1663,7 @@ public virtual void Can_insert_and_read_back_all_mapped_nullable_data_types_with @p47='4294967295' (Nullable = true) @p48='-1' (Nullable = true) @p49='-1' (Nullable = true) -@p50='18446744073709551615' (Nullable = true) +@p50='18446744073709551615' (Nullable = true) (Precision = 20) @p51='-1' (Nullable = true)", parameters, ignoreLineEndingDifferences: true); @@ -1691,8 +1691,8 @@ private static void AssertMappedNullableDataTypesWithIdentity(MappedNullableData Assert.Equal(84.4f, entity.FloatAsReal); Assert.Equal(85.5, entity.DoubleAsDoublePrecision); Assert.Equal(new DateTime(2015, 1, 2), entity.DateTimeAsDate); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime2); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(2345678), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(2345678), entity.DateTimeAsDatetime2); Assert.Equal(new DateTime(2018, 1, 2, 13, 11, 00), entity.DateTimeAsSmalldatetime); Assert.Equal(new DateTime(2019, 1, 2, 14, 11, 12), entity.DateTimeAsDatetime); Assert.Equal(new TimeSpan(11, 15, 12), entity.TimeSpanAsTime); @@ -1748,8 +1748,8 @@ private static MappedNullableDataTypesWithIdentity CreateMappedNullableDataTypes FloatAsReal = 84.4f, DoubleAsDoublePrecision = 85.5, DateTimeAsDate = new DateTime(2015, 1, 2, 10, 11, 12), - DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12), + DateTimeOffsetAsDatetimeoffset = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12).AddTicks(2345678), TimeSpan.Zero), + DateTimeAsDatetime2 = new DateTime(2017, 1, 2, 12, 11, 12).AddTicks(2345678), DateTimeAsSmalldatetime = new DateTime(2018, 1, 2, 13, 11, 12), DateTimeAsDatetime = new DateTime(2019, 1, 2, 14, 11, 12), TimeSpanAsTime = new TimeSpan(11, 15, 12), @@ -1818,10 +1818,10 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null_w @p16=NULL (DbType = DateTime2) @p17=NULL (DbType = DateTime) @p18=NULL (DbType = DateTimeOffset) -@p19=NULL -@p20=NULL +@p19=NULL (Precision = 18) (Scale = 2) +@p20=NULL (Precision = 18) (Scale = 2) @p21=NULL -@p22=NULL +@p22=NULL (Precision = 18) (Scale = 2) @p23=NULL @p24=NULL @p25=NULL @@ -1849,7 +1849,7 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null_w @p47=NULL (DbType = Int64) @p48=NULL (DbType = Int32) @p49=NULL (DbType = Int64) -@p50=NULL +@p50=NULL (Precision = 20) @p51=NULL (DbType = Int16)", parameters, ignoreLineEndingDifferences: true); @@ -2085,11 +2085,11 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_scale_wi var parameters = DumpParameters(); Assert.Equal( - @"@p0='2017-01-02T12:11:12' (Size = 3) -@p1='2016-01-02T11:11:12.0000000+00:00' (Size = 3) -@p2='102.2' (Size = 3) -@p3='101.1' (Size = 3) -@p4='103.3' (Size = 3) + @"@p0='2017-01-02T12:11:12.1230000' (Precision = 3) +@p1='2016-01-02T11:11:12.5670000+00:00' (Precision = 3) +@p2='102' (Precision = 3) +@p3='101' (Precision = 3) +@p4='103' (Precision = 3) @p5='85.55000305175781' (Size = 25) @p6='85.5' (Size = 3) @p7='83.33000183105469' (Size = 25) @@ -2111,8 +2111,8 @@ private static void AssertMappedScaledDataTypesWithIdentity(MappedScaledDataType Assert.Equal(85.5f, entity.FloatAsDoublePrecision3); Assert.Equal(83.33f, entity.FloatAsFloat25); Assert.Equal(85.55f, entity.FloatAsDoublePrecision25); - Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset3); - Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12), entity.DateTimeAsDatetime23); + Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12, 567), TimeSpan.Zero), entity.DateTimeOffsetAsDatetimeoffset3); + Assert.Equal(new DateTime(2017, 1, 2, 12, 11, 12, 123), entity.DateTimeAsDatetime23); Assert.Equal(101m, entity.DecimalAsDecimal3); Assert.Equal(102m, entity.DecimalAsDec3); Assert.Equal(103m, entity.DecimalAsNumeric3); @@ -2126,11 +2126,11 @@ private static MappedScaledDataTypesWithIdentity CreateMappedScaledDataTypesWith FloatAsDoublePrecision3 = 85.5f, FloatAsFloat25 = 83.33f, FloatAsDoublePrecision25 = 85.55f, - DateTimeOffsetAsDatetimeoffset3 = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), - DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12), - DecimalAsDecimal3 = 101.1m, - DecimalAsDec3 = 102.2m, - DecimalAsNumeric3 = 103.3m + DateTimeOffsetAsDatetimeoffset3 = new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12, 567), TimeSpan.Zero), + DateTimeAsDatetime23 = new DateTime(2017, 1, 2, 12, 11, 12, 123), + DecimalAsDecimal3 = 101m, + DecimalAsDec3 = 102m, + DecimalAsNumeric3 = 103m }; [ConditionalFact] @@ -2146,9 +2146,9 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_precisio var parameters = DumpParameters(); Assert.Equal( - @"@p0='102.2' -@p1='101.1' -@p2='103.3' + @"@p0='102.2' (Precision = 5) (Scale = 2) +@p1='101.1' (Precision = 5) (Scale = 2) +@p2='103.3' (Precision = 5) (Scale = 2) @p3='77'", parameters, ignoreLineEndingDifferences: true); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs index 4c25fa22175..58e0c3d99b7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs @@ -178,8 +178,8 @@ public override void FromSqlRaw_queryable_multiple_composed_with_closure_paramet base.FromSqlRaw_queryable_multiple_composed_with_closure_parameters(); AssertSql( - @"p0='1997-01-01T00:00:00' -p1='1998-01-01T00:00:00' + @"p0='1997-01-01T00:00:00.0000000' +p1='1998-01-01T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( @@ -197,8 +197,8 @@ public override void FromSqlRaw_queryable_multiple_composed_with_parameters_and_ AssertSql( @"p0='London' (Size = 4000) -p1='1997-01-01T00:00:00' -p2='1998-01-01T00:00:00' +p1='1997-01-01T00:00:00.0000000' +p2='1998-01-01T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( @@ -210,8 +210,8 @@ CROSS JOIN ( WHERE [c].[CustomerID] = [o].[CustomerID]", // @"p0='Berlin' (Size = 4000) -p1='1998-04-01T00:00:00' -p2='1998-05-01T00:00:00' +p1='1998-04-01T00:00:00.0000000' +p2='1998-05-01T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( @@ -296,8 +296,8 @@ public override void FromSqlInterpolated_queryable_multiple_composed_with_parame AssertSql( @"p0='London' (Size = 4000) -p1='1997-01-01T00:00:00' -p2='1998-01-01T00:00:00' +p1='1997-01-01T00:00:00.0000000' +p2='1998-01-01T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( @@ -309,8 +309,8 @@ CROSS JOIN ( WHERE [c].[CustomerID] = [o].[CustomerID]", // @"p0='Berlin' (Size = 4000) -p1='1998-04-01T00:00:00' -p2='1998-05-01T00:00:00' +p1='1998-04-01T00:00:00.0000000' +p2='1998-05-01T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index a7cb439b0d3..bfe3dd71b5a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -6630,7 +6630,7 @@ public override async Task DateTimeOffset_Date_returns_datetime(bool async) await base.DateTimeOffset_Date_returns_datetime(async); AssertSql( - @"@__dateTimeOffset_Date_0='0002-03-01T00:00:00' + @"@__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs index e5bf93faff7..f8299461b41 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs @@ -723,7 +723,7 @@ await AssertCount( c => dateTime > new DateTime(DateTime.Now.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond)); AssertSql( - @$"@__dateTime_0='1919-12-12T10:20:15' (DbType = DateTime) + @$"@__dateTime_0='1919-12-12T10:20:15.0000000' (DbType = DateTime) @__dateTime_Month_2='12' @__dateTime_Day_3='12' @__dateTime_Hour_4='10' @@ -783,7 +783,7 @@ await AssertCount( c => date > new DateTime(DateTime.Now.Year, date.Month, date.Day)); AssertSql( - @$"@__date_0='1919-12-12T00:00:00' (DbType = Date) + @$"@__date_0='1919-12-12T00:00:00.0000000' (DbType = Date) @__date_Month_2='12' @__date_Day_3='12' @@ -839,7 +839,7 @@ public virtual void DateTime2FromParts_compare_with_local_variable() Assert.Equal(0, count); AssertSql( - @$"@__dateTime_0='1919-12-12T10:20:15' + @$"@__dateTime_0='1919-12-12T10:20:15.0000000' @__dateTime_Month_2='12' @__dateTime_Day_3='12' @__dateTime_Hour_4='10' @@ -965,7 +965,7 @@ await AssertCount( c => dateTime > new DateTime(DateTime.Now.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0)); AssertSql( - @$"@__dateTime_0='1919-12-12T23:20:00' (DbType = DateTime) + @$"@__dateTime_0='1919-12-12T23:20:00.0000000' (DbType = DateTime) @__dateTime_Month_2='12' @__dateTime_Day_3='12' @__dateTime_Hour_4='23' diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs index 47660310fe5..2dbc3e0fd1e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs @@ -1395,7 +1395,7 @@ public override async Task Static_equals_nullable_datetime_compared_to_non_nulla await base.Static_equals_nullable_datetime_compared_to_non_nullable(async); AssertSql( - @"@__arg_0='1996-07-04T00:00:00' + @"@__arg_0='1996-07-04T00:00:00.0000000' SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 75783a7f824..43ca415c325 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -2696,7 +2696,7 @@ public override async Task DateTime_parse_is_parameterized_when_from_closure(boo await base.DateTime_parse_is_parameterized_when_from_closure(async); AssertSql( - @"@__Parse_0='1998-01-01T12:00:00' (Nullable = true) (DbType = DateTime) + @"@__Parse_0='1998-01-01T12:00:00.0000000' (Nullable = true) (DbType = DateTime) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] @@ -2718,13 +2718,13 @@ public override async Task New_DateTime_is_parameterized_when_from_closure(bool await base.New_DateTime_is_parameterized_when_from_closure(async); AssertSql( - @"@__p_0='1998-01-01T12:00:00' (Nullable = true) (DbType = DateTime) + @"@__p_0='1998-01-01T12:00:00.0000000' (Nullable = true) (DbType = DateTime) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] WHERE [o].[OrderDate] > @__p_0", // - @"@__p_0='1998-01-01T11:00:00' (Nullable = true) (DbType = DateTime) + @"@__p_0='1998-01-01T11:00:00.0000000' (Nullable = true) (DbType = DateTime) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index fd392709b44..e50cb387860 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -711,7 +711,7 @@ public override async Task Where_datetime_now(bool async) await base.Where_datetime_now(async); AssertSql( - @"@__myDatetime_0='2015-04-10T00:00:00' + @"@__myDatetime_0='2015-04-10T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] @@ -723,7 +723,7 @@ public override async Task Where_datetime_utcnow(bool async) await base.Where_datetime_utcnow(async); AssertSql( - @"@__myDatetime_0='2015-04-10T00:00:00' + @"@__myDatetime_0='2015-04-10T00:00:00.0000000' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] @@ -745,7 +745,7 @@ public override async Task Where_datetime_date_component(bool async) await base.Where_datetime_date_component(async); AssertSql( - @"@__myDatetime_0='1998-05-04T00:00:00' (DbType = DateTime) + @"@__myDatetime_0='1998-05-04T00:00:00.0000000' (DbType = DateTime) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs index acf87173385..c3cf4752b2a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs @@ -114,7 +114,7 @@ public override void Scalar_Function_Where_Not_Correlated_Static() base.Scalar_Function_Where_Not_Correlated_Static(); AssertSql( - @"@__startDate_0='2000-04-01T00:00:00' (Nullable = true) + @"@__startDate_0='2000-04-01T00:00:00.0000000' (Nullable = true) SELECT TOP(2) [c].[Id] FROM [Customers] AS [c] @@ -336,7 +336,7 @@ public override void Scalar_Function_Where_Not_Correlated_Instance() base.Scalar_Function_Where_Not_Correlated_Instance(); AssertSql( - @"@__startDate_1='2000-04-01T00:00:00' (Nullable = true) + @"@__startDate_1='2000-04-01T00:00:00.0000000' (Nullable = true) SELECT TOP(2) [c].[Id] FROM [Customers] AS [c] diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs index db74901434b..c1d0481fc11 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs @@ -63,6 +63,24 @@ public override void AddColumnOperation_with_maxLength_overridden() "); } + public override void AddColumnOperation_with_precision_and_scale_overridden() + { + base.AddColumnOperation_with_precision_and_scale_overridden(); + + AssertSql( + @"ALTER TABLE [Person] ADD [Pi] decimal(15,10) NOT NULL; +"); + } + + public override void AddColumnOperation_with_precision_and_scale_no_model() + { + base.AddColumnOperation_with_precision_and_scale_no_model(); + + AssertSql( + @"ALTER TABLE [Person] ADD [Pi] decimal(20,7) NOT NULL; +"); + } + public override void AddColumnOperation_with_unicode_overridden() { base.AddColumnOperation_with_unicode_overridden(); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index c82a7de6b44..cf0e3f04557 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -49,7 +49,7 @@ public override async Task Where_datetime_now(bool async) await base.Where_datetime_now(async); AssertSql( - @"@__myDatetime_0='2015-04-10T00:00:00' (DbType = String) + @"@__myDatetime_0='2015-04-10T00:00:00.0000000' (DbType = String) SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" @@ -61,7 +61,7 @@ public override async Task Where_datetime_utcnow(bool async) await base.Where_datetime_utcnow(async); AssertSql( - @"@__myDatetime_0='2015-04-10T00:00:00' (DbType = String) + @"@__myDatetime_0='2015-04-10T00:00:00.0000000' (DbType = String) SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" @@ -83,7 +83,7 @@ public override async Task Where_datetime_date_component(bool async) await base.Where_datetime_date_component(async); AssertSql( - @"@__myDatetime_0='1998-05-04T00:00:00' (DbType = String) + @"@__myDatetime_0='1998-05-04T00:00:00.0000000' (DbType = String) SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" diff --git a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs index 06947228ca5..b3c3b90e18a 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs @@ -126,6 +126,68 @@ public void Can_only_override_existing_MaxLength_value_explicitly() Assert.Equal(2, metadata.GetMaxLength().Value); } + [ConditionalFact] + public void Can_only_override_lower_or_equal_source_Precision() + { + var builder = CreateInternalPropertyBuilder(); + var metadata = builder.Metadata; + + Assert.NotNull(builder.HasPrecision(1, ConfigurationSource.DataAnnotation)); + Assert.NotNull(builder.HasPrecision(2, ConfigurationSource.DataAnnotation)); + + Assert.Equal(2, metadata.GetPrecision().Value); + + Assert.Null(builder.HasPrecision(1, ConfigurationSource.Convention)); + Assert.Equal(2, metadata.GetPrecision().Value); + } + + [ConditionalFact] + public void Can_only_override_existing_Precision_value_explicitly() + { + var metadata = CreateProperty(); + metadata.SetPrecision(1); + var builder = metadata.Builder; + + Assert.NotNull(builder.HasPrecision(1, ConfigurationSource.DataAnnotation)); + Assert.Null(builder.HasPrecision(2, ConfigurationSource.DataAnnotation)); + + Assert.Equal(1, metadata.GetPrecision().Value); + + Assert.NotNull(builder.HasPrecision(2, ConfigurationSource.Explicit)); + Assert.Equal(2, metadata.GetPrecision().Value); + } + + [ConditionalFact] + public void Can_only_override_lower_or_equal_source_Scale() + { + var builder = CreateInternalPropertyBuilder(); + var metadata = builder.Metadata; + + Assert.NotNull(builder.HasScale(1, ConfigurationSource.DataAnnotation)); + Assert.NotNull(builder.HasScale(2, ConfigurationSource.DataAnnotation)); + + Assert.Equal(2, metadata.GetScale().Value); + + Assert.Null(builder.HasScale(1, ConfigurationSource.Convention)); + Assert.Equal(2, metadata.GetScale().Value); + } + + [ConditionalFact] + public void Can_only_override_existing_Scale_value_explicitly() + { + var metadata = CreateProperty(); + metadata.SetScale(1); + var builder = metadata.Builder; + + Assert.NotNull(builder.HasScale(1, ConfigurationSource.DataAnnotation)); + Assert.Null(builder.HasScale(2, ConfigurationSource.DataAnnotation)); + + Assert.Equal(1, metadata.GetScale().Value); + + Assert.NotNull(builder.HasScale(2, ConfigurationSource.Explicit)); + Assert.Equal(2, metadata.GetScale().Value); + } + [ConditionalFact] public void Can_only_override_lower_or_equal_source_CustomValueGenerator_factory() {