diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index d9d4076df80..14a7e6651f7 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -344,16 +344,20 @@ private string CreateModelBuilder( mainBuilder.AppendLine(); } - CreateAnnotations( - model, - _annotationCodeGenerator.Generate, - new CSharpRuntimeAnnotationCodeGeneratorParameters( - "this", - className, - mainBuilder, - methodBuilder, - namespaces, - variables)); + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( + "this", + className, + mainBuilder, + methodBuilder, + namespaces, + variables); + + foreach (var typeConfiguration in model.GetScalarTypeConfigurations()) + { + Create(typeConfiguration, parameters); + } + + CreateAnnotations(model, _annotationCodeGenerator.Generate, parameters); } mainBuilder @@ -378,6 +382,81 @@ private string CreateModelBuilder( return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder; } + private void Create( + IScalarTypeConfiguration typeConfiguration, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var variableName = _code.Identifier("type", parameters.ScopeVariables, capitalize: false); + + var mainBuilder = parameters.MainBuilder; + mainBuilder + .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddScalarTypeConfiguration(") + .IncrementIndent() + .Append(_code.Literal(typeConfiguration.ClrType)); + + AddNamespace(typeConfiguration.ClrType, parameters.Namespaces); + + if (typeConfiguration.GetMaxLength() != null) + { + mainBuilder.AppendLine(",") + .Append("maxLength: ") + .Append(_code.Literal(typeConfiguration.GetMaxLength())); + } + + if (typeConfiguration.IsUnicode() != null) + { + mainBuilder.AppendLine(",") + .Append("unicode: ") + .Append(_code.Literal(typeConfiguration.IsUnicode())); + } + + if (typeConfiguration.GetPrecision() != null) + { + mainBuilder.AppendLine(",") + .Append("precision: ") + .Append(_code.Literal(typeConfiguration.GetPrecision())); + } + + if (typeConfiguration.GetScale() != null) + { + mainBuilder.AppendLine(",") + .Append("scale: ") + .Append(_code.Literal(typeConfiguration.GetScale())); + } + + var providerClrType = typeConfiguration.GetProviderClrType(); + if (providerClrType != null) + { + AddNamespace(providerClrType, parameters.Namespaces); + + mainBuilder.AppendLine(",") + .Append("providerPropertyType: ") + .Append(_code.Literal(providerClrType)); + } + + var valueConverterType = (Type?)typeConfiguration[CoreAnnotationNames.ValueConverterType]; + if (valueConverterType != null) + { + AddNamespace(valueConverterType, parameters.Namespaces); + + mainBuilder.AppendLine(",") + .Append("valueConverter: new ") + .Append(_code.Reference(valueConverterType)) + .Append("()"); + } + + mainBuilder + .AppendLine(");") + .DecrementIndent(); + + CreateAnnotations( + typeConfiguration, + _annotationCodeGenerator.Generate, + parameters with { TargetName = variableName }); + + mainBuilder.AppendLine(); + } + private string GenerateEntityType(IEntityType entityType, string @namespace, string className, bool nullable) { var mainBuilder = new IndentedStringBuilder(); @@ -468,12 +547,12 @@ private void CreateEntityType( namespaces, variables); - Create(entityType, parameters, className); + Create(entityType, parameters); var propertyVariables = new Dictionary(); foreach (var property in entityType.GetDeclaredProperties()) { - Create(property, propertyVariables, parameters, className); + Create(property, propertyVariables, parameters); } foreach (var property in entityType.GetDeclaredServiceProperties()) @@ -501,7 +580,7 @@ private void CreateEntityType( .AppendLine("}"); } - private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, string className) + private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var runtimeEntityType = entityType as IRuntimeEntityType; if ((entityType.ConstructorBinding is not null @@ -512,7 +591,7 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator || runtimeEntityType.ServiceOnlyConstructorBinding is FactoryMethodBinding))) { throw new InvalidOperationException(DesignStrings.CompiledModelConstructorBinding( - entityType.ShortName(), "Customize()", className)); + entityType.ShortName(), "Customize()", parameters.ClassName)); } if (entityType.GetQueryFilter() != null) @@ -590,8 +669,7 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator private void Create( IProperty property, Dictionary propertyVariables, - CSharpRuntimeAnnotationCodeGeneratorParameters parameters, - string className) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var valueGeneratorFactoryType = (Type?)property[CoreAnnotationNames.ValueGeneratorFactoryType]; if (valueGeneratorFactoryType == null @@ -625,17 +703,12 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelTypeMapping( - property.DeclaringEntityType.ShortName(), property.Name, "Customize()", className)); + property.DeclaringEntityType.ShortName(), property.Name, "Customize()", parameters.ClassName)); } var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); propertyVariables[property] = variableName; - if (property.ClrType.Namespace != null) - { - parameters.Namespaces.Add(property.ClrType.Namespace); - } - var mainBuilder = parameters.MainBuilder; mainBuilder .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddProperty(") @@ -710,10 +783,7 @@ private void Create( var providerClrType = property.GetProviderClrType(); if (providerClrType != null) { - if (providerClrType.Namespace != null) - { - parameters.Namespaces.Add(providerClrType.Namespace); - } + AddNamespace(providerClrType, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("providerPropertyType: ") @@ -722,10 +792,7 @@ private void Create( if (valueGeneratorFactoryType != null) { - if (valueGeneratorFactoryType.Namespace != null) - { - parameters.Namespaces.Add(valueGeneratorFactoryType.Namespace); - } + AddNamespace(valueGeneratorFactoryType, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("valueGeneratorFactory: new ") @@ -735,10 +802,7 @@ private void Create( if (valueConverterType != null) { - if (valueConverterType.Namespace != null) - { - parameters.Namespaces.Add(valueConverterType.Namespace); - } + AddNamespace(valueConverterType, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("valueConverter: new ") @@ -748,10 +812,7 @@ private void Create( if (valueComparerType != null) { - if (valueComparerType.Namespace != null) - { - parameters.Namespaces.Add(valueComparerType.Namespace); - } + AddNamespace(valueComparerType, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("valueComparer: new ") @@ -788,10 +849,7 @@ private void PropertyBaseParameters( var propertyInfo = property.PropertyInfo; if (propertyInfo != null) { - if (propertyInfo.DeclaringType?.Namespace != null) - { - parameters.Namespaces.Add(propertyInfo.DeclaringType.Namespace); - } + AddNamespace(propertyInfo.DeclaringType!, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("propertyInfo: "); @@ -818,10 +876,7 @@ private void PropertyBaseParameters( var fieldInfo = property.FieldInfo; if (fieldInfo != null) { - if (fieldInfo.DeclaringType?.Namespace != null) - { - parameters.Namespaces.Add(fieldInfo.DeclaringType.Namespace); - } + AddNamespace(fieldInfo.DeclaringType!, parameters.Namespaces); mainBuilder.AppendLine(",") .Append("fieldInfo: ") diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs index 47a00e02fb9..1f4c353d34b 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs @@ -36,14 +36,14 @@ public CSharpRuntimeAnnotationCodeGenerator(CSharpRuntimeAnnotationCodeGenerator /// public virtual void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { - if (parameters.IsRuntime) + if (!parameters.IsRuntime) { - parameters.Annotations.Remove(CoreAnnotationNames.ModelDependencies); - parameters.Annotations.Remove(CoreAnnotationNames.ReadOnlyModel); + parameters.Annotations.Remove(CoreAnnotationNames.PropertyAccessMode); } else { - parameters.Annotations.Remove(CoreAnnotationNames.PropertyAccessMode); + parameters.Annotations.Remove(CoreAnnotationNames.ModelDependencies); + parameters.Annotations.Remove(CoreAnnotationNames.ReadOnlyModel); } GenerateSimpleAnnotations(parameters); @@ -69,21 +69,13 @@ public virtual void Generate(IProperty property, CSharpRuntimeAnnotationCodeGene if (!parameters.IsRuntime) { var annotations = parameters.Annotations; - annotations.Remove(CoreAnnotationNames.PropertyAccessMode); - annotations.Remove(CoreAnnotationNames.BeforeSaveBehavior); - annotations.Remove(CoreAnnotationNames.AfterSaveBehavior); - annotations.Remove(CoreAnnotationNames.MaxLength); - annotations.Remove(CoreAnnotationNames.Unicode); - annotations.Remove(CoreAnnotationNames.Precision); - annotations.Remove(CoreAnnotationNames.Scale); - annotations.Remove(CoreAnnotationNames.ProviderClrType); - annotations.Remove(CoreAnnotationNames.ValueGeneratorFactory); - annotations.Remove(CoreAnnotationNames.ValueGeneratorFactoryType); - annotations.Remove(CoreAnnotationNames.ValueConverter); - annotations.Remove(CoreAnnotationNames.ValueConverterType); - annotations.Remove(CoreAnnotationNames.ValueComparer); - annotations.Remove(CoreAnnotationNames.ValueComparerType); - annotations.Remove(CoreAnnotationNames.PreUniquificationName); + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } } GenerateSimpleAnnotations(parameters); @@ -95,7 +87,13 @@ public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationC if (!parameters.IsRuntime) { var annotations = parameters.Annotations; - annotations.Remove(CoreAnnotationNames.PropertyAccessMode); + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } } GenerateSimpleAnnotations(parameters); @@ -104,12 +102,36 @@ public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationC /// public virtual void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } + } + GenerateSimpleAnnotations(parameters); } /// public virtual void Generate(IForeignKey foreignKey, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } + } + GenerateSimpleAnnotations(parameters); } @@ -119,8 +141,13 @@ public virtual void Generate(INavigation navigation, CSharpRuntimeAnnotationCode if (!parameters.IsRuntime) { var annotations = parameters.Annotations; - annotations.Remove(CoreAnnotationNames.PropertyAccessMode); - annotations.Remove(CoreAnnotationNames.EagerLoaded); + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } } GenerateSimpleAnnotations(parameters); @@ -132,8 +159,13 @@ public virtual void Generate(ISkipNavigation navigation, CSharpRuntimeAnnotation if (!parameters.IsRuntime) { var annotations = parameters.Annotations; - annotations.Remove(CoreAnnotationNames.PropertyAccessMode); - annotations.Remove(CoreAnnotationNames.EagerLoaded); + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } } GenerateSimpleAnnotations(parameters); @@ -142,6 +174,36 @@ public virtual void Generate(ISkipNavigation navigation, CSharpRuntimeAnnotation /// public virtual void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + + /// + public virtual void Generate(IScalarTypeConfiguration typeConfiguration, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + foreach (var annotation in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(annotation.Key)) + { + annotations.Remove(annotation.Key); + } + } + } + GenerateSimpleAnnotations(parameters); } diff --git a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs index e187a17eb99..1b07598ecde 100644 --- a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs @@ -72,5 +72,12 @@ public interface ICSharpRuntimeAnnotationCodeGenerator /// The index to which the annotations are applied. /// Additional parameters used during code generation. void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + + /// + /// Generates code to create the given annotations. + /// + /// The scalar type configuration to which the annotations are applied. + /// Additional parameters used during code generation. + void Generate(IScalarTypeConfiguration typeConfiguration, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); } } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 7588208f1b0..0a2702285a7 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; #nullable enable @@ -256,14 +257,21 @@ protected virtual void ProcessEntityTypeAnnotations( } private RuntimeScalarTypeConfiguration Create(IScalarTypeConfiguration typeConfiguration, RuntimeModel model) - => model.AddScalarTypeConfiguration( - typeConfiguration.ClrType, - typeConfiguration.GetMaxLength(), - typeConfiguration.IsUnicode(), - typeConfiguration.GetPrecision(), - typeConfiguration.GetScale(), - typeConfiguration.GetProviderClrType(), - (Type?)typeConfiguration[CoreAnnotationNames.ValueConverterType]); + { + var valueConverterType = (Type?)typeConfiguration[CoreAnnotationNames.ValueConverterType]; + var valueConverter = valueConverterType == null + ? null + : (ValueConverter?)Activator.CreateInstance(valueConverterType); + + return model.AddScalarTypeConfiguration( + typeConfiguration.ClrType, + typeConfiguration.GetMaxLength(), + typeConfiguration.IsUnicode(), + typeConfiguration.GetPrecision(), + typeConfiguration.GetScale(), + typeConfiguration.GetProviderClrType(), + valueConverter); + } /// /// Updates the property annotations that will be set on the read-only object. diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 2911c083223..851035215a5 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -147,7 +147,7 @@ private IEnumerable FindEntityTypes(Type type) /// /// The type that the property value will be converted to before being sent to the database provider. /// - /// The type of a custom set for this property type. + /// The custom for this type. /// The newly created property. public virtual RuntimeScalarTypeConfiguration AddScalarTypeConfiguration( Type clrType, @@ -156,7 +156,7 @@ public virtual RuntimeScalarTypeConfiguration AddScalarTypeConfiguration( int? precision = null, int? scale = null, Type? providerPropertyType = null, - Type? valueConverterType = null) + ValueConverter? valueConverter = null) { var typeConfiguration = new RuntimeScalarTypeConfiguration( clrType, @@ -165,7 +165,7 @@ public virtual RuntimeScalarTypeConfiguration AddScalarTypeConfiguration( precision, scale, providerPropertyType, - valueConverterType); + valueConverter); _typeConfigurations.Add(clrType, typeConfiguration); diff --git a/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs b/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs index ecdb828cc12..6af9761e9a5 100644 --- a/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs +++ b/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs @@ -29,7 +29,7 @@ public RuntimeScalarTypeConfiguration( int? precision, int? scale, Type? providerClrType, - Type? valueConverterType) + ValueConverter? valueConverter) { ClrType = clrType; @@ -58,10 +58,7 @@ public RuntimeScalarTypeConfiguration( SetAnnotation(CoreAnnotationNames.ProviderClrType, providerClrType); } - if (valueConverterType != null) - { - _valueConverter = (ValueConverter?)Activator.CreateInstance(valueConverterType); - } + _valueConverter = valueConverter; } /// diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index cb7e8292486..01d1e8c1456 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1901,6 +1901,11 @@ partial void Initialize() DataEntityType.CreateAnnotations(data); ObjectEntityType.CreateAnnotations(@object); + var type = this.AddScalarTypeConfiguration( + typeof(string), + maxLength: 256); + type.AddAnnotation(""Relational:IsFixedLength"", true); + var functions = new SortedDictionary(); var getBlobs = new RuntimeDbFunction( ""GetBlobs()"", @@ -1937,7 +1942,7 @@ partial void Initialize() ""condition"", typeof(string), false, - ""nvarchar(256)""); + ""nchar(256)""); functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; @@ -1997,7 +2002,7 @@ partial void Initialize() ""date"", typeof(string), false, - ""nvarchar(256)""); + ""nchar(256)""); isDateStatic.AddAnnotation(""MyGuid"", new Guid(""00000000-0000-0000-0000-000000000000"")); functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; @@ -2144,12 +2149,12 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var getCountParameter2 = getCount.Parameters[1]; Assert.Same(getCount, getCountParameter2.Function); Assert.Equal("condition", getCountParameter2.Name); - Assert.Equal("nvarchar(256)", getCountParameter2.StoreType); + Assert.Equal("nchar(256)", getCountParameter2.StoreType); Assert.False(getCountParameter2.PropagatesNullability); Assert.Equal(typeof(string), getCountParameter2.ClrType); - Assert.Equal("nvarchar(256)", getCountParameter2.TypeMapping.StoreType); + Assert.Equal("nchar(256)", getCountParameter2.TypeMapping.StoreType); Assert.Equal("condition", getCountParameter2.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(256)", getCountParameter2.StoreFunctionParameter.Type); + Assert.Equal("nchar(256)", getCountParameter2.StoreFunctionParameter.Type); Assert.NotNull(getCountParameter2.ToString()); var isDate = model.FindDbFunction(typeof(DbFunctionContext).GetMethod("IsDateStatic")); @@ -2173,12 +2178,12 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var isDateParameter = isDate.Parameters[0]; Assert.Same(isDate, isDateParameter.Function); Assert.Equal("date", isDateParameter.Name); - Assert.Equal("nvarchar(256)", isDateParameter.StoreType); + Assert.Equal("nchar(256)", isDateParameter.StoreType); Assert.False(isDateParameter.PropagatesNullability); Assert.Equal(typeof(string), isDateParameter.ClrType); - Assert.Equal("nvarchar(256)", isDateParameter.TypeMapping.StoreType); + Assert.Equal("nchar(256)", isDateParameter.TypeMapping.StoreType); Assert.Equal("date", isDateParameter.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(256)", isDateParameter.StoreFunctionParameter.Type); + Assert.Equal("nchar(256)", isDateParameter.StoreFunctionParameter.Type); var getData = model.FindDbFunction(typeof(DbFunctionContext) .GetMethod("GetData", new Type[] { typeof(int) })); @@ -2293,7 +2298,7 @@ public IQueryable GetData() protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { - configurationBuilder.Scalars().HaveMaxLength(256); + configurationBuilder.Scalars().HaveMaxLength(256).AreFixedLength(); } protected override void OnModelCreating(ModelBuilder modelBuilder)