From 5cdae726728e8303aff5b845b00b6eb5acff20c2 Mon Sep 17 00:00:00 2001 From: HuyLuong Date: Sat, 12 Sep 2020 18:40:05 +0700 Subject: [PATCH] Support Unicode Attribute Add Unicode annotation and the capability of generating UnicodeAttribute when scaffolding. --- src/EFCore.Abstractions/UnicodeAttribute.cs | 28 ++++++++ .../Internal/CSharpEntityTypeGenerator.cs | 24 +++++++ .../ProviderConventionSetBuilder.cs | 2 + .../Conventions/UnicodeAttributeConvention.cs | 41 ++++++++++++ .../Internal/CSharpEntityTypeGeneratorTest.cs | 50 ++++++++++++++ .../DataAnnotationTestBase.cs | 24 +++++++ .../PropertyAttributeConventionTest.cs | 65 +++++++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 src/EFCore.Abstractions/UnicodeAttribute.cs create mode 100644 src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs diff --git a/src/EFCore.Abstractions/UnicodeAttribute.cs b/src/EFCore.Abstractions/UnicodeAttribute.cs new file mode 100644 index 00000000000..b973fc57fb0 --- /dev/null +++ b/src/EFCore.Abstractions/UnicodeAttribute.cs @@ -0,0 +1,28 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Configures the property as capable of persisting unicode characters. Can only be set on System.String properties. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class UnicodeAttribute : Attribute + { + /// + /// A value indicating whether the property can contain unicode characters or not. + /// + public bool IsUnicode { get; } + + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether the property can contain unicode characters or not. + public UnicodeAttribute(bool isUnicode = true) + { + IsUnicode = isUnicode; + } + } +} diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index 505e4bb3337..0b6977b0065 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -287,6 +287,7 @@ protected virtual void GeneratePropertyDataAnnotations([NotNull] IProperty prope GenerateRequiredAttribute(property); GenerateColumnAttribute(property); GenerateMaxLengthAttribute(property); + GenerateUnicodeAttribute(property); var annotations = _annotationCodeGenerator .FilterIgnoredAnnotations(property.GetAnnotations()) @@ -365,6 +366,29 @@ private void GenerateMaxLengthAttribute(IProperty property) } } + private void GenerateUnicodeAttribute(IProperty property) + { + if (property.ClrType != typeof(string)) + { + return; + } + + var isUnicode = property.IsUnicode(); + + if (isUnicode.HasValue) + { + if (!isUnicode.Value) + { + var unicodeAttribute = new AttributeWriter(nameof(UnicodeAttribute)); + + unicodeAttribute.AddParameter(_code.Literal(false)); + + _sb.AppendLine(unicodeAttribute.ToString()); + } + else _sb.AppendLine(new AttributeWriter(nameof(UnicodeAttribute)).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/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 411b54f8be0..d4203620973 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -113,6 +113,7 @@ public virtual ConventionSet CreateConventionSet() var stringLengthAttributeConvention = new StringLengthAttributeConvention(Dependencies); var timestampAttributeConvention = new TimestampAttributeConvention(Dependencies); var backingFieldAttributeConvention = new BackingFieldAttributeConvention(Dependencies); + var unicodeAttributeConvention = new UnicodeAttributeConvention(Dependencies); conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention); conventionSet.PropertyAddedConventions.Add(backingFieldConvention); @@ -126,6 +127,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.PropertyAddedConventions.Add(keyAttributeConvention); conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention); conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention); + conventionSet.PropertyAddedConventions.Add(unicodeAttributeConvention); conventionSet.EntityTypePrimaryKeyChangedConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.EntityTypePrimaryKeyChangedConventions.Add(valueGeneratorConvention); diff --git a/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs new file mode 100644 index 00000000000..c62cc824ba2 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs @@ -0,0 +1,41 @@ +// 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 Unicode based on the applied on the property. + /// + public class UnicodeAttributeConvention : PropertyAttributeConventionBase + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public UnicodeAttributeConvention([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, + UnicodeAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + propertyBuilder.IsUnicode(unicode: attribute.IsUnicode, fromDataAnnotation: true); + } + } +} diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 72ad7fb1783..b09ea26fcd7 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -846,6 +846,56 @@ public partial class Entity }); } + [ConditionalFact] + public void UnicodeAttribute_is_generated_for_property() + { + Test( + modelBuilder => modelBuilder + .Entity( + "Entity", + x => + { + x.Property("Id"); + x.Property("A").HasMaxLength(34).IsUnicode(); + x.Property("B").HasMaxLength(34).IsUnicode(false); + }), + 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; } + [StringLength(34)] + [Unicode] + public string A { get; set; } + [StringLength(34)] + [Unicode(false)] + public string B { get; set; } + } +} +", + code.AdditionalFiles.Single(f => f.Path == "Entity.cs")); + }, + model => + { + var entitType = model.FindEntityType("TestNamespace.Entity"); + Assert.True(entitType.GetProperty("A").IsUnicode()); + Assert.False(entitType.GetProperty("B").IsUnicode()); + }); + } + [ConditionalFact] public void Properties_are_sorted_in_order_of_definition_in_table() { diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 8e1334daf82..0d8bb0cb754 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -2375,6 +2375,30 @@ public virtual void TimestampAttribute_throws_if_value_in_database_changed() }); } + [ConditionalFact] + public virtual void Unicode_annotation_is_enabled() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + + Validate(modelBuilder); + + Assert.True(GetProperty(modelBuilder, "PersonFirstName").IsUnicode()); + Assert.False(GetProperty(modelBuilder, "PersonLastName").IsUnicode()); + } + + protected class UnicodeAnnotationClass + { + public int Id { get; set; } + + [Unicode] + public string PersonFirstName { get; set; } + + [Unicode(false)] + public string PersonLastName { get; set; } + } + [ConditionalFact] public virtual void OwnedEntityTypeAttribute_configures_one_reference_as_owned() { diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs index 01120e2dfc0..85fa6309b6a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs @@ -543,6 +543,56 @@ public void BackingFieldAttribute_does_not_override_configuration_from_explicit_ #endregion + #region UnicodeAttribute + [ConditionalFact] + public void UnicodeAttribute_overrides_configuration_from_convention_source() + { + var entityTypeBuilder = CreateInternalEntityTypeBuilder(); + + var propertyBuilder = entityTypeBuilder.Property(typeof(string), "UnicodeProperty", ConfigurationSource.Explicit); + + propertyBuilder.IsUnicode(false, ConfigurationSource.Convention); + + RunConvention(propertyBuilder); + + Assert.True(propertyBuilder.Metadata.IsUnicode()); + } + + [ConditionalFact] + public void UnicodeAttribute_does_not_override_configuration_from_explicit_source() + { + var entityTypeBuilder = CreateInternalEntityTypeBuilder(); + + var propertyBuilder = entityTypeBuilder.Property(typeof(string), "UnicodeProperty", ConfigurationSource.Explicit); + + propertyBuilder.IsUnicode(false, ConfigurationSource.Explicit); + + RunConvention(propertyBuilder); + + Assert.False(propertyBuilder.Metadata.IsUnicode()); + } + + [ConditionalFact] + public void UnicodeAttribute_sets_unicode_with_conventional_builder() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + + Assert.True(entityTypeBuilder.Property(e => e.UnicodeProperty).Metadata.IsUnicode()); + Assert.False(entityTypeBuilder.Property(e => e.NonUnicodeProperty).Metadata.IsUnicode()); + } + + [ConditionalFact] + public void UnicodeAttribute_on_field_sets_unicode_with_conventional_builder() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + + Assert.True(entityTypeBuilder.Property(nameof(F.UnicodeProperty)).Metadata.IsUnicode()); + Assert.False(entityTypeBuilder.Property(nameof(F.NonUnicodeProperty)).Metadata.IsUnicode()); + } + #endregion + [ConditionalFact] public void Property_attribute_convention_runs_for_private_property() { @@ -595,6 +645,9 @@ private static void RunConvention(InternalPropertyBuilder propertyBuilder) new KeyAttributeConvention(dependencies) .ProcessPropertyAdded(propertyBuilder, context); + + new UnicodeAttributeConvention(dependencies) + .ProcessPropertyAdded(propertyBuilder, context); } private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) @@ -628,6 +681,12 @@ private class A [Required] public string Name { get; set; } + [Unicode] + public string UnicodeProperty { get; set; } + + [Unicode(false)] + public string NonUnicodeProperty { get; set; } + [Key] public int MyPrimaryKey { get; set; } @@ -683,6 +742,12 @@ public class F [Required] public string Name; + [Unicode] + public string UnicodeProperty { get; set; } + + [Unicode(false)] + public string NonUnicodeProperty { get; set; } + [Key] public int MyPrimaryKey;