diff --git a/src/EFCore.Abstractions/PrecisionAttribute.cs b/src/EFCore.Abstractions/PrecisionAttribute.cs new file mode 100644 index 00000000000..f7ba482b7d3 --- /dev/null +++ b/src/EFCore.Abstractions/PrecisionAttribute.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Configures 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. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public class PrecisionAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The precision of the property. + /// The scale of the property. + public PrecisionAttribute(int precision, int scale) + { + if (precision < 0) + { + throw new ArgumentException(AbstractionsStrings.ArgumentIsNegativeNumber(nameof(precision))); + } + + if (scale < 0) + { + throw new ArgumentException(AbstractionsStrings.ArgumentIsNegativeNumber(nameof(scale))); + } + + Precision = precision; + Scale = scale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The precision of the property. + public PrecisionAttribute(int precision) + { + if (precision < 0) + { + throw new ArgumentException(AbstractionsStrings.ArgumentIsNegativeNumber(nameof(precision))); + } + + Precision = precision; + } + + /// + /// The precision of the property. + /// + public int Precision { get; } + + /// + /// The scale of the property. + /// + public int? Scale { get; } + } +} diff --git a/src/EFCore.Abstractions/Properties/AbstractionsStrings.Designer.cs b/src/EFCore.Abstractions/Properties/AbstractionsStrings.Designer.cs index e322de38345..0ca04113d9e 100644 --- a/src/EFCore.Abstractions/Properties/AbstractionsStrings.Designer.cs +++ b/src/EFCore.Abstractions/Properties/AbstractionsStrings.Designer.cs @@ -29,6 +29,14 @@ public static string ArgumentIsEmpty([CanBeNull] object argumentName) GetString("ArgumentIsEmpty", nameof(argumentName)), argumentName); + /// + /// The number argument '{argumentName}' cannot be negative number. + /// + public static string ArgumentIsNegativeNumber([CanBeNull] object argumentName) + => string.Format( + GetString("ArgumentIsNegativeNumber", nameof(argumentName)), + argumentName); + /// /// The collection argument '{argumentName}' must not contain any empty elements. /// diff --git a/src/EFCore.Abstractions/Properties/AbstractionsStrings.resx b/src/EFCore.Abstractions/Properties/AbstractionsStrings.resx index ad0e3c343b3..7bdf6f59663 100644 --- a/src/EFCore.Abstractions/Properties/AbstractionsStrings.resx +++ b/src/EFCore.Abstractions/Properties/AbstractionsStrings.resx @@ -120,6 +120,9 @@ The string argument '{argumentName}' cannot be empty. + + The number argument '{argumentName}' cannot be negative number. + The collection argument '{argumentName}' must not contain any empty elements. diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index d66e3a818fe..3e21d3c4f05 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -595,25 +595,25 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) lines.Add( $".{nameof(PropertyBuilder.HasMaxLength)}({_code.Literal(maxLength.Value)})"); } - } - 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)})"); - } + 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( - $".{nameof(PropertyBuilder.IsUnicode)}({(property.IsUnicode() == false ? "false" : "")})"); + if (property.IsUnicode() != null) + { + lines.Add( + $".{nameof(PropertyBuilder.IsUnicode)}({(property.IsUnicode() == false ? "false" : "")})"); + } } var defaultValue = property.GetDefaultValue(); diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index 8f70fa8a790..950844b6968 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -293,6 +293,7 @@ protected virtual void GeneratePropertyDataAnnotations([NotNull] IProperty prope GenerateColumnAttribute(property); GenerateMaxLengthAttribute(property); GenerateUnicodeAttribute(property); + GeneratePrecisionAttribute(property); var annotations = _annotationCodeGenerator .FilterIgnoredAnnotations(property.GetAnnotations()) @@ -390,6 +391,24 @@ private void GenerateUnicodeAttribute(IProperty property) } } + private void GeneratePrecisionAttribute(IProperty property) + { + var precision = property.GetPrecision(); + if (precision.HasValue) + { + var precisionAttribute = new AttributeWriter(nameof(PrecisionAttribute)); + precisionAttribute.AddParameter(_code.Literal(precision.Value)); + + var scale = property.GetScale(); + if (scale.HasValue) + { + precisionAttribute.AddParameter(_code.Literal(scale.Value)); + } + + _sb.AppendLine(precisionAttribute.ToString()); + } + } + /// /// 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/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 971be5eadde..f207465ff08 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -182,6 +182,46 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder /// if the capability of persisting unicode characters can be configured for this property. bool CanSetIsUnicode(bool? unicode, bool fromDataAnnotation = false); + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionPropertyBuilder HasPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the precision of data allowed can be set for this property + /// from the current configuration source. + /// + /// The precision of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the precision of data allowed can be set for this property. + bool CanSetPrecision(int? precision, bool fromDataAnnotation = false); + + /// + /// Configures the scale of the property. + /// + /// The scale of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + IConventionPropertyBuilder HasScale(int? scale, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the scale of data allowed can be set for this property + /// from the current configuration source. + /// + /// The scale of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the scale of data allowed can be set for this property. + bool CanSetScale(int? scale, bool fromDataAnnotation = false); + /// /// Configures whether this property can be modified before the entity is saved to the database. /// diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index d4203620973..bf7c3273cca 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -114,6 +114,7 @@ public virtual ConventionSet CreateConventionSet() var timestampAttributeConvention = new TimestampAttributeConvention(Dependencies); var backingFieldAttributeConvention = new BackingFieldAttributeConvention(Dependencies); var unicodeAttributeConvention = new UnicodeAttributeConvention(Dependencies); + var precisionAttributeConvention = new PrecisionAttributeConvention(Dependencies); conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention); conventionSet.PropertyAddedConventions.Add(backingFieldConvention); @@ -128,6 +129,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(unicodeAttributeConvention); + conventionSet.PropertyAddedConventions.Add(precisionAttributeConvention); conventionSet.EntityTypePrimaryKeyChangedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.EntityTypePrimaryKeyChangedConventions.Add(valueGeneratorConvention); diff --git a/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs new file mode 100644 index 00000000000..12e14aec393 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that configures the Precision based on the applied on the property. + /// + public class PrecisionAttributeConvention : PropertyAttributeConventionBase + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public PrecisionAttributeConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. + /// + /// The builder for the property. + /// The attribute. + /// The member that has the attribute. + /// Additional information associated with convention execution. + protected override void ProcessPropertyAdded( + IConventionPropertyBuilder propertyBuilder, + PrecisionAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + propertyBuilder.HasPrecision(attribute.Precision, fromDataAnnotation: true); + + if (attribute.Scale.HasValue) + { + propertyBuilder.HasScale(attribute.Scale, fromDataAnnotation: true); + } + } + } +} diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 7161fc37207..5da17744b13 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -858,6 +858,42 @@ IConventionPropertyBuilder IConventionPropertyBuilder.IsUnicode(bool? unicode, b bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnnotation) => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// 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. + /// + IConventionPropertyBuilder IConventionPropertyBuilder.HasPrecision(int? precision, bool fromDataAnnotation) + => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// 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. + /// + bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnnotation) + => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// 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. + /// + IConventionPropertyBuilder IConventionPropertyBuilder.HasScale(int? scale, bool fromDataAnnotation) + => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// 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. + /// + bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) + => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// 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/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 849e9f2c29a..9c4314f9100 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -375,6 +375,28 @@ public void ComputedColumnSql_works() }); } + [ConditionalFact] + public void IsUnicode_works() + { + Test( + modelBuilder => { + modelBuilder.Entity("Entity").Property("UnicodeColumn").IsUnicode(); + modelBuilder.Entity("Entity").Property("NonUnicodeColumn").IsUnicode(false); + }, + new ModelCodeGenerationOptions(), + code => { + Assert.Contains("Property(e => e.UnicodeColumn).IsUnicode()", code.ContextFile.Code); + Assert.Contains("Property(e => e.NonUnicodeColumn).IsUnicode(false)", code.ContextFile.Code); + }, + model => + { + var entity = model.FindEntityType("TestNamespace.Entity"); + Assert.True(entity.GetProperty("UnicodeColumn").IsUnicode()); + Assert.False(entity.GetProperty("NonUnicodeColumn").IsUnicode()); + }); + } + + [ConditionalFact] public void ComputedColumnSql_works_stored() { diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 99ea4f5f595..913d0b25682 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -901,6 +901,63 @@ public partial class Entity }); } + [ConditionalFact] + public void PrecisionAttribute_is_generated_for_property() + { + Test( + modelBuilder => modelBuilder + .Entity( + "Entity", + x => + { + x.Property("Id"); + x.Property("A").HasPrecision(10); + x.Property("B").HasPrecision(14, 3); + x.Property("C").HasPrecision(5); + x.Property("D").HasPrecision(3); + }), + new ModelCodeGenerationOptions { UseDataAnnotations = true }, + code => + { + AssertFileContents( + @"using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +#nullable disable + +namespace TestNamespace +{ + public partial class Entity + { + [Key] + public int Id { get; set; } + [Precision(10)] + public decimal A { get; set; } + [Precision(14, 3)] + public decimal B { get; set; } + [Precision(5)] + public DateTime C { get; set; } + [Precision(3)] + public DateTimeOffset D { get; set; } + } +} +", + code.AdditionalFiles.Single(f => f.Path == "Entity.cs")); + }, + model => + { + var entitType = model.FindEntityType("TestNamespace.Entity"); + Assert.Equal(10, entitType.GetProperty("A").GetPrecision()); + Assert.Equal(14, entitType.GetProperty("B").GetPrecision()); + Assert.Equal(3, entitType.GetProperty("B").GetScale()); + Assert.Equal(5, entitType.GetProperty("C").GetPrecision()); + Assert.Equal(3, entitType.GetProperty("D").GetPrecision()); + }); + } + [ConditionalFact] public void Comments_are_generated() { diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 2bba7f319ab..ff93e1580e0 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -2415,6 +2415,54 @@ protected class UnicodeAnnotationClass public string PersonAddress; } + [ConditionalFact] + public virtual void PrecisionAttribute_sets_precision_for_properties_and_fields() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(b => + { + b.Property(e => e.DecimalField); + b.Property(e => e.DateTimeField); + b.Property(e => e.DateTimeOffsetField); + }); + + Validate(modelBuilder); + + Assert.Equal(10, GetProperty(modelBuilder, "DecimalProperty").GetPrecision()); + Assert.Equal(2, GetProperty(modelBuilder, "DecimalProperty").GetScale()); + Assert.Equal(5, GetProperty(modelBuilder, "DateTimeProperty").GetPrecision()); + Assert.Equal(5, GetProperty(modelBuilder, "DateTimeOffsetProperty").GetPrecision()); + + Assert.Equal(10, GetProperty(modelBuilder, "DecimalField").GetPrecision()); + Assert.Equal(2, GetProperty(modelBuilder, "DecimalField").GetScale()); + Assert.Equal(5, GetProperty(modelBuilder, "DateTimeField").GetPrecision()); + Assert.Equal(5, GetProperty(modelBuilder, "DateTimeOffsetField").GetPrecision()); + } + + protected class PrecisionAnnotationClass + { + public int Id { get; set; } + + [Precision(10, 2)] + public decimal DecimalProperty { get; set; } + + [Precision(5)] + public DateTime DateTimeProperty { get; set; } + + [Precision(5)] + public DateTimeOffset DateTimeOffsetProperty { get; set; } + + [Precision(10, 2)] + public string DecimalField; + + [Precision(5)] + public DateTime DateTimeField; + + [Precision(5)] + public DateTimeOffset DateTimeOffsetField; + } + [ConditionalFact] public virtual void OwnedEntityTypeAttribute_configures_one_reference_as_owned() { diff --git a/test/EFCore.Tests/Infrastructure/AnnotationTest.cs b/test/EFCore.Tests/Infrastructure/AnnotationTest.cs index 0d9b208edb1..22a605de932 100644 --- a/test/EFCore.Tests/Infrastructure/AnnotationTest.cs +++ b/test/EFCore.Tests/Infrastructure/AnnotationTest.cs @@ -26,5 +26,19 @@ public void Can_create_annotation() Assert.Equal("Foo", annotation.Name); Assert.Equal("Bar", annotation.Value); } + + [ConditionalFact] + public void NegativeNumberArguments_PrecisionAttribute_Throws() + { + Assert.Equal( + AbstractionsStrings.ArgumentIsNegativeNumber("precision"), + Assert.Throws(() => new PrecisionAttribute(-1)).Message); + Assert.Equal( + AbstractionsStrings.ArgumentIsNegativeNumber("scale"), + Assert.Throws(() => new PrecisionAttribute(3, -2)).Message); + Assert.Equal( + AbstractionsStrings.ArgumentIsNegativeNumber("precision"), + Assert.Throws(() => new PrecisionAttribute(-5, 4)).Message); + } } } diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs index 727b1e18354..cbb17bdbaf8 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs @@ -594,6 +594,59 @@ public void UnicodeAttribute_on_field_sets_unicode_with_conventional_builder() } #endregion + #region PrecisionAttribute + [ConditionalFact] + public void PrecisionAttribute_overrides_configuration_from_convention_source() + { + var entityTypeBuilder = CreateInternalEntityTypeBuilder(); + + var propertyBuilder = entityTypeBuilder.Property(typeof(decimal), "DecimalProperty", ConfigurationSource.Explicit); + + propertyBuilder.HasPrecision(12, ConfigurationSource.Convention); + propertyBuilder.HasScale(5, ConfigurationSource.Convention); + + RunConvention(propertyBuilder); + + Assert.Equal(10, propertyBuilder.Metadata.GetPrecision()); + Assert.Equal(2, propertyBuilder.Metadata.GetScale()); + } + + [ConditionalFact] + public void PrecisionAttribute_does_not_override_configuration_from_explicit_source() + { + var entityTypeBuilder = CreateInternalEntityTypeBuilder(); + + var propertyBuilder = entityTypeBuilder.Property(typeof(decimal), "DecimalProperty", ConfigurationSource.Explicit); + + propertyBuilder.HasPrecision(12, ConfigurationSource.Explicit); + propertyBuilder.HasScale(5, ConfigurationSource.Explicit); + + RunConvention(propertyBuilder); + + Assert.Equal(12, propertyBuilder.Metadata.GetPrecision()); + Assert.Equal(5, propertyBuilder.Metadata.GetScale()); + } + + [ConditionalFact] + public void PrecisionAttribute_sets_precision_with_conventional_builder() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + + Assert.Equal(10, entityTypeBuilder.Property(e => e.DecimalProperty).Metadata.GetPrecision()); + Assert.Equal(2, entityTypeBuilder.Property(e => e.DecimalProperty).Metadata.GetScale()); + } + + [ConditionalFact] + public void PrecisionAttribute_on_field_sets_precision_with_conventional_builder() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + + Assert.Equal(10, entityTypeBuilder.Property(nameof(F.DecimalField)).Metadata.GetPrecision()); + Assert.Equal(2, entityTypeBuilder.Property(nameof(F.DecimalField)).Metadata.GetScale()); + } + #endregion [ConditionalFact] public void Property_attribute_convention_runs_for_private_property() { @@ -649,6 +702,9 @@ private static void RunConvention(InternalPropertyBuilder propertyBuilder) new UnicodeAttributeConvention(dependencies) .ProcessPropertyAdded(propertyBuilder, context); + + new PrecisionAttributeConvention(dependencies) + .ProcessPropertyAdded(propertyBuilder, context); } private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) @@ -688,6 +744,9 @@ private class A [Unicode(false)] public string NonUnicodeProperty { get; set; } + [Precision(10, 2)] + public decimal DecimalProperty { get; set; } + [Key] public int MyPrimaryKey { get; set; } @@ -749,6 +808,9 @@ public class F [Unicode(false)] public string NonUnicodeField; + [Precision(10, 2)] + public decimal DecimalField; + [Key] public int MyPrimaryKey;