diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 10237c7410e..1dc52c82723 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -16,7 +16,16 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Design; public class CSharpSnapshotGenerator : ICSharpSnapshotGenerator { private static readonly MethodInfo HasAnnotationMethodInfo - = typeof(ModelBuilder).GetRuntimeMethod(nameof(ModelBuilder.HasAnnotation), new[] { typeof(string), typeof(string) })!; + = typeof(ModelBuilder).GetRuntimeMethod(nameof(ModelBuilder.HasAnnotation), + new[] { typeof(string), typeof(string) })!; + + private static readonly MethodInfo HasPropertyAnnotationMethodInfo + = typeof(ComplexPropertyBuilder).GetRuntimeMethod(nameof(ComplexPropertyBuilder.HasPropertyAnnotation), + new[] { typeof(string), typeof(string) })!; + + private static readonly MethodInfo HasTypeAnnotationMethodInfo + = typeof(ComplexPropertyBuilder).GetRuntimeMethod(nameof(ComplexPropertyBuilder.HasTypeAnnotation), + new[] { typeof(string), typeof(string) })!; /// /// Initializes a new instance of the class. @@ -102,11 +111,11 @@ protected virtual void GenerateEntityTypes( /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateEntityType( - string modelBuilderName, + string builderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { @@ -121,10 +130,10 @@ protected virtual void GenerateEntityType( entityTypeName = entityType.ClrType.DisplayName(); } - var entityTypeBuilderName = GenerateEntityTypeBuilderName(); + var entityTypeBuilderName = GenerateNestedBuilderName(builderName); stringBuilder - .Append(modelBuilderName) + .Append(builderName) .Append( ownerNavigation != null ? ownership!.IsUnique ? ".OwnsOne(" : ".OwnsMany(" @@ -153,6 +162,8 @@ protected virtual void GenerateEntityType( GenerateProperties(entityTypeBuilderName, entityType.GetDeclaredProperties(), stringBuilder); + GenerateComplexProperties(entityTypeBuilderName, entityType.GetDeclaredComplexProperties(), stringBuilder); + GenerateKeys( entityTypeBuilderName, entityType.GetDeclaredKeys(), @@ -179,24 +190,6 @@ protected virtual void GenerateEntityType( stringBuilder .AppendLine("});"); } - - string GenerateEntityTypeBuilderName() - { - if (modelBuilderName.StartsWith("b", StringComparison.Ordinal)) - { - // ReSharper disable once InlineOutVariableDeclaration - var counter = 1; - if (modelBuilderName.Length > 1 - && int.TryParse(modelBuilderName[1..], out counter)) - { - counter++; - } - - return "b" + (counter == 0 ? "" : counter.ToString()); - } - - return "b"; - } } /// @@ -527,6 +520,114 @@ protected virtual void GeneratePropertyAnnotations( ?? (property.FindTypeMapping() ?? Dependencies.RelationalTypeMappingSource.FindMapping(property))?.Converter; + /// + /// Generates code for objects. + /// + /// The name of the builder variable. + /// The properties. + /// The builder code is added to. + protected virtual void GenerateComplexProperties( + string typeBuilderName, + IEnumerable properties, + IndentedStringBuilder stringBuilder) + { + foreach (var property in properties) + { + GenerateComplexProperty(typeBuilderName, property, stringBuilder); + } + } + + /// + /// Generates code for an . + /// + /// The name of the builder variable. + /// The entity type. + /// The builder code is added to. + protected virtual void GenerateComplexProperty( + string builderName, + IComplexProperty complexProperty, + IndentedStringBuilder stringBuilder) + { + var complexType = complexProperty.ComplexType; + var complexTypeBuilderName = GenerateNestedBuilderName(builderName); + + stringBuilder + .AppendLine() + .Append(builderName) + .Append($".ComplexProperty<{Code.Reference(Model.DefaultPropertyBagType)}>(") + .Append($"{Code.Literal(complexProperty.Name)}, {Code.Literal(complexType.Name)}, ") + .Append(complexTypeBuilderName) + .AppendLine(" =>"); + + using (stringBuilder.Indent()) + { + stringBuilder.Append("{"); + + using (stringBuilder.Indent()) + { + if (complexProperty.IsNullable != complexProperty.ClrType.IsNullableType()) + { + stringBuilder + .AppendLine() + .Append(".IsRequired()"); + } + + GenerateProperties(complexTypeBuilderName, complexType.GetDeclaredProperties(), stringBuilder); + + GenerateComplexProperties(complexTypeBuilderName, complexType.GetDeclaredComplexProperties(), stringBuilder); + + GenerateComplexPropertyAnnotations(complexTypeBuilderName, complexProperty, stringBuilder); + } + + stringBuilder + .AppendLine("});"); + } + } + + private static string GenerateNestedBuilderName(string builderName) + { + if (builderName.StartsWith("b", StringComparison.Ordinal)) + { + // ReSharper disable once InlineOutVariableDeclaration + var counter = 1; + if (builderName.Length > 1 + && int.TryParse(builderName[1..], out counter)) + { + counter++; + } + + return "b" + (counter == 0 ? "" : counter.ToString()); + } + + return "b"; + } + + /// + /// Generates code for the annotations on an . + /// + /// The name of the builder variable. + /// The property. + /// The builder code is added to. + protected virtual void GenerateComplexPropertyAnnotations( + string propertyBuilderName, + IComplexProperty property, + IndentedStringBuilder stringBuilder) + { + var propertyAnnotations = Dependencies.AnnotationCodeGenerator + .FilterIgnoredAnnotations(property.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + + var typeAnnotations = Dependencies.AnnotationCodeGenerator + .FilterIgnoredAnnotations(property.ComplexType.GetAnnotations()) + .ToDictionary(a => a.Name, a => a); + + GenerateAnnotations(propertyBuilderName, property, stringBuilder, propertyAnnotations, + inChainedCall: false, hasAnnotationMethodInfo: HasPropertyAnnotationMethodInfo); + + GenerateAnnotations(propertyBuilderName, property, stringBuilder, typeAnnotations, + inChainedCall: false, hasAnnotationMethodInfo: HasTypeAnnotationMethodInfo); + } + /// /// Generates code for objects. /// @@ -1763,7 +1864,8 @@ private void GenerateAnnotations( IndentedStringBuilder stringBuilder, Dictionary annotations, bool inChainedCall, - bool leadingNewline = true) + bool leadingNewline = true, + MethodInfo? hasAnnotationMethodInfo = null) { var fluentApiCalls = Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(annotatable, annotations); @@ -1789,7 +1891,8 @@ private void GenerateAnnotations( // Append remaining raw annotations which did not get generated as Fluent API calls foreach (var annotation in annotations.Values.OrderBy(a => a.Name)) { - var call = new MethodCallCodeFragment(HasAnnotationMethodInfo, annotation.Name, annotation.Value); + var call = new MethodCallCodeFragment(hasAnnotationMethodInfo ?? HasAnnotationMethodInfo, + annotation.Name, annotation.Value); chainedCall = chainedCall is null ? call : chainedCall.Chain(call); } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index 03677cb78b4..10c20b759e1 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.Migrations.Design; /// @@ -171,10 +174,17 @@ private static IEnumerable GetAnnotatables(IEnumerableThe namespaces. protected virtual IEnumerable GetNamespaces(IModel model) => model.GetEntityTypes().SelectMany( - e => e.GetDeclaredProperties() - .SelectMany(p => (FindValueConverter(p)?.ProviderClrType ?? p.ClrType).GetNamespaces())) + e => GetNamespaces(e) + .Concat(e.GetDeclaredComplexProperties().Any() + ? Model.DefaultPropertyBagType.GetNamespaces() + : Enumerable.Empty())) .Concat(GetAnnotationNamespaces(GetAnnotatables(model))); + private IEnumerable GetNamespaces(ITypeBase typeBase) + => typeBase.GetDeclaredProperties() + .SelectMany(p => (FindValueConverter(p)?.ProviderClrType ?? p.ClrType).GetNamespaces()) + .Concat(typeBase.GetDeclaredComplexProperties().SelectMany(p => GetNamespaces(p.ComplexType))); + private static IEnumerable GetAnnotatables(IModel model) { yield return model; diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index e506eae509b..9f72d0b44c4 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -87,6 +87,12 @@ public virtual void RemoveAnnotationsHandledByConventions( RemoveConventionalAnnotationsHelper(entityType, annotations, IsHandledByConvention); } + /// + public virtual void RemoveAnnotationsHandledByConventions( + IComplexType complexType, + IDictionary annotations) + => RemoveConventionalAnnotationsHelper(complexType, annotations, IsHandledByConvention); + /// public virtual void RemoveAnnotationsHandledByConventions( IEntityTypeMappingFragment fragment, @@ -107,6 +113,12 @@ public virtual void RemoveAnnotationsHandledByConventions( RemoveConventionalAnnotationsHelper(property, annotations, IsHandledByConvention); } + /// + public virtual void RemoveAnnotationsHandledByConventions( + IComplexProperty complexProperty, + IDictionary annotations) + => RemoveConventionalAnnotationsHelper(complexProperty, annotations, IsHandledByConvention); + /// public virtual void RemoveAnnotationsHandledByConventions(IKey key, IDictionary annotations) { @@ -236,6 +248,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IComplexType complexType, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexType, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + /// public virtual IReadOnlyList GenerateFluentApiCalls( IEntityTypeMappingFragment fragment, @@ -315,6 +339,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls( return methodCallCodeFragments; } + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IComplexProperty complexProperty, + IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexProperty, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + /// public virtual IReadOnlyList GenerateFluentApiCalls( IKey key, @@ -512,6 +548,19 @@ protected virtual bool IsHandledByConvention(IModel model, IAnnotation annotatio protected virtual bool IsHandledByConvention(IEntityType entityType, IAnnotation annotation) => false; + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(IComplexType complexType, IAnnotation annotation) + => false; + /// /// Checks if the given is handled by convention when /// applied to the given . @@ -551,6 +600,19 @@ protected virtual bool IsHandledByConvention(IKey key, IAnnotation annotation) protected virtual bool IsHandledByConvention(IProperty property, IAnnotation annotation) => false; + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual bool IsHandledByConvention(IComplexProperty complexProperty, IAnnotation annotation) + => false; + /// /// Checks if the given is handled by convention when /// applied to the given . @@ -681,6 +743,19 @@ protected virtual bool IsHandledByConvention(ISequence sequence, IAnnotation ann protected virtual MethodCallCodeFragment? GenerateFluentApi(IEntityType entityType, IAnnotation annotation) => null; + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(IComplexType complexType, IAnnotation annotation) + => null; + /// /// Returns a fluent API call for the given , or /// if no fluent API call exists for it. @@ -720,6 +795,19 @@ protected virtual bool IsHandledByConvention(ISequence sequence, IAnnotation ann protected virtual MethodCallCodeFragment? GenerateFluentApi(IProperty property, IAnnotation annotation) => null; + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// + /// The . + /// The . + /// . + protected virtual MethodCallCodeFragment? GenerateFluentApi(IComplexProperty complexProperty, IAnnotation annotation) + => null; + /// /// Returns a fluent API call for the given , or /// if no fluent API call exists for it. diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index 9309eb085ae..777d421be92 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -42,9 +42,19 @@ void RemoveAnnotationsHandledByConventions(IModel model, IDictionary - /// The entity to which the annotations are applied. + /// The entity type to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveAnnotationsHandledByConventions(IEntityType entityType, IDictionary annotations) + { + } + + /// + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. + /// + /// The complex type to which the annotations are applied. /// The set of annotations from which to remove the conventional ones. - void RemoveAnnotationsHandledByConventions(IEntityType entity, IDictionary annotations) + void RemoveAnnotationsHandledByConventions(IComplexType complexType, IDictionary annotations) { } @@ -68,6 +78,16 @@ void RemoveAnnotationsHandledByConventions(IProperty property, IDictionary + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. + /// + /// The complex property to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveAnnotationsHandledByConventions(IComplexProperty complexProperty, IDictionary annotations) + { + } + /// /// Removes annotation whose configuration is already applied by convention, and do not need to be /// specified explicitly. @@ -182,6 +202,10 @@ internal sealed void RemoveAnnotationsHandledByConventionsInternal( RemoveAnnotationsHandledByConventions(entityType, annotations); return; + case IComplexType complexType: + RemoveAnnotationsHandledByConventions(complexType, annotations); + return; + case IEntityTypeMappingFragment fragment: RemoveAnnotationsHandledByConventions(fragment, annotations); return; @@ -190,6 +214,10 @@ internal sealed void RemoveAnnotationsHandledByConventionsInternal( RemoveAnnotationsHandledByConventions(property, annotations); return; + case IComplexProperty complexProperty: + RemoveAnnotationsHandledByConventions(complexProperty, annotations); + return; + case IKey key: RemoveAnnotationsHandledByConventions(key, annotations); return; @@ -253,6 +281,17 @@ IReadOnlyList GenerateFluentApiCalls( IDictionary annotations) => Array.Empty(); + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The entity type to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + IComplexType complexType, + IDictionary annotations) + => Array.Empty(); + /// /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls /// and removes the annotations. @@ -275,6 +314,17 @@ IReadOnlyList GenerateFluentApiCalls( IDictionary annotations) => Array.Empty(); + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The complex property to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + IComplexProperty complexProperty, + IDictionary annotations) + => Array.Empty(); + /// /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls /// and removes the annotations. @@ -391,8 +441,10 @@ internal sealed IReadOnlyList GenerateFluentApiCallsInte { IModel model => GenerateFluentApiCalls(model, annotations), IEntityType entityType => GenerateFluentApiCalls(entityType, annotations), + IComplexType complexType => GenerateFluentApiCalls(complexType, annotations), IEntityTypeMappingFragment fragment => GenerateFluentApiCalls(fragment, annotations), IProperty property => GenerateFluentApiCalls(property, annotations), + IComplexProperty complexProperty => GenerateFluentApiCalls(complexProperty, annotations), IRelationalPropertyOverrides overrides => GenerateFluentApiCalls(overrides, annotations), IKey key => GenerateFluentApiCalls(key, annotations), IForeignKey foreignKey => GenerateFluentApiCalls(foreignKey, annotations), diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index a364761733e..3ace197146d 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -2778,11 +2778,11 @@ protected virtual void ValidateJsonEntityProperties( /// available, indicating possible reasons why the property cannot be mapped. /// /// The property CLR type. - /// The entity type. + /// The structural type. /// The property. protected override void ThrowPropertyNotMappedException( string propertyType, - IConventionEntityType entityType, + IConventionTypeBase typeBase, IConventionProperty unmappedProperty) { var storeType = unmappedProperty.GetColumnType(); @@ -2791,11 +2791,11 @@ protected override void ThrowPropertyNotMappedException( throw new InvalidOperationException( RelationalStrings.PropertyNotMapped( propertyType, - entityType.DisplayName(), + typeBase.DisplayName(), unmappedProperty.Name, storeType)); } - base.ThrowPropertyNotMappedException(propertyType, entityType, unmappedProperty); + base.ThrowPropertyNotMappedException(propertyType, typeBase, unmappedProperty); } } diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 1598b9c3486..7578b9f6618 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Security.Cryptography; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -136,52 +137,81 @@ protected virtual void ValidatePropertyMapping( foreach (var entityType in conventionModel.GetEntityTypes()) { - var unmappedProperty = entityType.GetDeclaredProperties().FirstOrDefault( - p => (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()) - // Use a better condition for non-persisted properties when issue #14121 is implemented - || !p.IsImplicitlyCreated()) - && p.FindTypeMapping() == null); + Validate(entityType); + } + + void Validate(IConventionTypeBase typeBase) + { + var unmappedProperty = typeBase.GetDeclaredProperties().FirstOrDefault( + p => (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()) + // Use a better condition for non-persisted properties when issue #14121 is implemented + || !p.IsImplicitlyCreated()) + && p.FindTypeMapping() == null); if (unmappedProperty != null) { ThrowPropertyNotMappedException( (unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName(), - entityType, + typeBase, unmappedProperty); } - if (entityType.ClrType == Model.DefaultPropertyBagType) + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) { - continue; + if (complexProperty.IsShadowProperty()) + { + throw new InvalidOperationException( + CoreStrings.ComplexPropertyShadow(typeBase.DisplayName(), complexProperty.Name)); + } + + if (complexProperty.IsIndexerProperty()) + { + throw new InvalidOperationException( + CoreStrings.ComplexPropertyIndexer(typeBase.DisplayName(), complexProperty.Name)); + } + + if (complexProperty.IsCollection) + { + throw new InvalidOperationException( + CoreStrings.ComplexPropertyCollection(typeBase.DisplayName(), complexProperty.Name)); + } + + if (complexProperty.ComplexType.GetMembers().Count() == 0) + { + throw new InvalidOperationException( + CoreStrings.EmptyComplexType(complexProperty.ComplexType.DisplayName())); + } + + Validate(complexProperty.ComplexType); } - var runtimeProperties = entityType.GetRuntimeProperties(); + if (typeBase.ClrType == Model.DefaultPropertyBagType) + { + return; + } + + var runtimeProperties = typeBase.GetRuntimeProperties(); var clrProperties = new HashSet(StringComparer.Ordinal); clrProperties.UnionWith( runtimeProperties.Values .Where(pi => pi.IsCandidateProperty(needsWrite: false)) .Select(pi => pi.GetSimpleMemberName())); - clrProperties.ExceptWith( - ((IEnumerable)entityType.GetProperties()) - .Concat(entityType.GetComplexProperties()) - .Concat(entityType.GetNavigations()) - .Concat(entityType.GetSkipNavigations()) - .Concat(entityType.GetServiceProperties()).Select(p => p.Name)); + clrProperties.ExceptWith(typeBase.GetMembers().Select(p => p.Name)); - if (entityType.IsPropertyBag) + if (typeBase.IsPropertyBag) { clrProperties.ExceptWith(DictionaryProperties); } if (clrProperties.Count <= 0) { - continue; + return; } foreach (var clrPropertyName in clrProperties) { - if (entityType.FindIgnoredConfigurationSource(clrPropertyName) != null) + if (typeBase.FindIgnoredConfigurationSource(clrPropertyName) != null) { continue; } @@ -212,6 +242,18 @@ protected virtual void ValidatePropertyMapping( { var targetShared = conventionModel.IsShared(targetType); targetOwned ??= IsOwned(targetType, conventionModel); + + if (typeBase is not IConventionEntityType entityType) + { + if (!((IReadOnlyComplexType)typeBase).IsInDeclarationPath(targetType)) + { + throw new InvalidOperationException( + CoreStrings.NavigationNotAddedComplexType( + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); + } + continue; + } + // ReSharper disable CheckForReferenceEqualityInstead.1 // ReSharper disable CheckForReferenceEqualityInstead.3 if ((isAdHoc @@ -230,21 +272,21 @@ protected virtual void ValidatePropertyMapping( { throw new InvalidOperationException( CoreStrings.AmbiguousOwnedNavigation( - entityType.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName())); + typeBase.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName())); } if (targetShared) { throw new InvalidOperationException( - CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, entityType.DisplayName())); + CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, typeBase.DisplayName())); } throw new InvalidOperationException( isAdHoc ? CoreStrings.NavigationNotAddedAdHoc( - entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()) + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()) : CoreStrings.NavigationNotAdded( - entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); } // ReSharper restore CheckForReferenceEqualityInstead.3 @@ -255,16 +297,16 @@ protected virtual void ValidatePropertyMapping( { throw new InvalidOperationException( CoreStrings.InterfacePropertyNotAdded( - entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); } else { throw new InvalidOperationException( isAdHoc ? CoreStrings.PropertyNotAddedAdHoc( - entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()) + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()) : CoreStrings.PropertyNotAdded( - entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); + typeBase.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())); } } } @@ -275,16 +317,16 @@ protected virtual void ValidatePropertyMapping( /// available, indicating possible reasons why the property cannot be mapped. /// /// The property CLR type. - /// The entity type. + /// The structural type. /// The property. protected virtual void ThrowPropertyNotMappedException( string propertyType, - IConventionEntityType entityType, + IConventionTypeBase typeBase, IConventionProperty unmappedProperty) => throw new InvalidOperationException( CoreStrings.PropertyNotMapped( propertyType, - entityType.DisplayName(), + typeBase.DisplayName(), unmappedProperty.Name)); /// @@ -640,13 +682,23 @@ protected virtual void ValidateChangeTrackingStrategy( var requireFullNotifications = (bool?)model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true; foreach (var entityType in model.GetEntityTypes()) { - var errorMessage = EntityType.CheckChangeTrackingStrategy( - entityType, entityType.GetChangeTrackingStrategy(), requireFullNotifications); + Validate(entityType, requireFullNotifications); + } + + static void Validate(ITypeBase typeBase, bool requireFullNotifications) + { + var errorMessage = TypeBase.CheckChangeTrackingStrategy( + typeBase, typeBase.GetChangeTrackingStrategy(), requireFullNotifications); if (errorMessage != null) { throw new InvalidOperationException(errorMessage); } + + foreach (var complexProperty in typeBase.GetComplexProperties()) + { + Validate(complexProperty.ComplexType, requireFullNotifications); + } } } @@ -778,9 +830,8 @@ protected virtual void ValidateForeignKeys( static bool ContainedInForeignKeyForAllConcreteTypes(IEntityType entityType, IProperty property) => entityType.ClrType.IsAbstract && entityType.GetDerivedTypes().Where(t => !t.ClrType.IsAbstract) - .All( - d => d.GetForeignKeys() - .Any(fk => fk.Properties.Contains(property))); + .All(d => d.GetForeignKeys() + .Any(fk => fk.Properties.Contains(property))); } /// @@ -802,15 +853,40 @@ protected virtual void ValidateFieldMapping( IDiagnosticsLogger logger) { foreach (var entityType in model.GetEntityTypes()) + { + Validate(entityType); + } + + static void Validate(ITypeBase typeBase) { var properties = new HashSet( - entityType - .GetDeclaredProperties() - .Cast() - .Concat(entityType.GetDeclaredNavigations()) - .Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty())); + typeBase + .GetDeclaredMembers() + .Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty())); + + var fieldProperties = new Dictionary(); + foreach (var propertyBase in properties) + { + var field = propertyBase.FieldInfo; + if (field == null) + { + continue; + } - var constructorBinding = entityType.ConstructorBinding; + if (fieldProperties.TryGetValue(field, out var conflictingProperty)) + { + throw new InvalidOperationException(CoreStrings.ConflictingFieldProperty( + propertyBase.DeclaringType.DisplayName(), + propertyBase.Name, + field.Name, + conflictingProperty.DeclaringType.DisplayName(), + conflictingProperty.Name)); + } + + fieldProperties.Add(field, propertyBase); + } + + var constructorBinding = typeBase.ConstructorBinding; if (constructorBinding != null) { foreach (var consumedProperty in constructorBinding.ParameterBindings.SelectMany(p => p.ConsumedProperties)) @@ -848,6 +924,11 @@ protected virtual void ValidateFieldMapping( throw new InvalidOperationException(errorMessage); } } + + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) + { + Validate(complexProperty.ComplexType); + } } } @@ -862,7 +943,12 @@ protected virtual void ValidateTypeMappings( { foreach (var entityType in model.GetEntityTypes()) { - foreach (var property in entityType.GetDeclaredProperties()) + Validate(entityType, logger); + } + + static void Validate(ITypeBase typeBase, IDiagnosticsLogger logger) + { + foreach (var property in typeBase.GetDeclaredProperties()) { var converter = property.GetValueConverter(); if (converter != null @@ -904,6 +990,11 @@ protected virtual void ValidateTypeMappings( actualProviderClrType.ShortDisplayName())); } } + + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) + { + Validate(complexProperty.ComplexType, logger); + } } } diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs index 9fbaf953795..750d8db0368 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -261,6 +261,7 @@ public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) TypeBuilder.ComplexProperty( propertyType: null, Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -283,6 +284,31 @@ public virtual ComplexPropertyBuilder ComplexProperty(stri TypeBuilder.ComplexProperty( typeof(TProperty), Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName, string complexTypeName) + => new( + TypeBuilder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -305,6 +331,31 @@ public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string TypeBuilder.ComplexProperty( Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName, string complexTypeName) + => new( + TypeBuilder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -354,6 +405,34 @@ public virtual ComplexPropertyBuilder ComplexProperty( return this; } + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, + string complexTypeName, + Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName, complexTypeName)); + + return this; + } + /// /// Configures a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. @@ -369,7 +448,10 @@ public virtual ComplexPropertyBuilder ComplexProperty( /// The name of the property to be configured. /// An action that performs configuration of the property. /// The same builder instance so that multiple configuration calls can be chained. - public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName, Action buildAction) + public virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, + string propertyName, + Action buildAction) { Check.NotNull(buildAction, nameof(buildAction)); @@ -378,6 +460,35 @@ public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string return this; } + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, + string propertyName, + string complexTypeName, + Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName, complexTypeName)); + + return this; + } + /// /// Excludes the given property from the complex type. This method is typically used to remove properties /// and navigations from the complex type that were added by convention. diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs index 868b5645c87..01c9b8066de 100644 --- a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -134,6 +134,28 @@ public virtual ComplexTypePropertyBuilder Property(Express string propertyName, Action> buildAction) => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, buildAction); + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, + string complexTypeName, + Action> buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, complexTypeName, buildAction); + /// /// Configures a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. @@ -153,6 +175,29 @@ public virtual ComplexTypePropertyBuilder Property(Express Type propertyType, string propertyName, Action buildAction) => (ComplexPropertyBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, + string propertyName, + string complexTypeName, + Action buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyType, complexTypeName, propertyName, buildAction); + /// /// Returns an object that can be used to configure a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. @@ -170,9 +215,38 @@ public virtual ComplexTypePropertyBuilder Property(Express /// blog => blog.Url). /// /// An object that can be used to configure the property. - public virtual ComplexPropertyBuilder ComplexProperty(Expression> propertyExpression) + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) => new(TypeBuilder.ComplexProperty( Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// The name of the complex type. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + => new(TypeBuilder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -204,6 +278,37 @@ public virtual ComplexPropertyBuilder ComplexProperty( return this; } + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression, complexTypeName)); + + return this; + } + /// /// Excludes the given property from the entity type. This method is typically used to remove properties /// or navigations from the entity type that were added by convention. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 65299bcc607..c0398c28246 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -228,6 +228,7 @@ public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) Builder.ComplexProperty( propertyType: null, Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -250,6 +251,33 @@ public virtual ComplexPropertyBuilder ComplexProperty(stri Builder.ComplexProperty( typeof(TProperty), Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, + string complexTypeName) + => new( + Builder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -272,6 +300,34 @@ public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string Builder.ComplexProperty( Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, + string propertyName, + string complexTypeName) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)!.Metadata); @@ -321,6 +377,34 @@ public virtual EntityTypeBuilder ComplexProperty( return this; } + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + string propertyName, + string complexTypeName, + Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName, complexTypeName)); + + return this; + } + /// /// Returns an object that can be used to configure a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. @@ -345,6 +429,35 @@ public virtual EntityTypeBuilder ComplexProperty(Type propertyType, string prope return this; } + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + Type propertyType, + string propertyName, + string complexTypeName, + Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName, complexTypeName)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 5e4061ab41a..1168686bba9 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -186,6 +186,28 @@ public virtual PropertyBuilder Property(Expression> buildAction) => (EntityTypeBuilder)base.ComplexProperty(propertyName, buildAction); + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + string propertyName, + string complexTypeName, + Action> buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyName, complexTypeName, buildAction); + /// /// Returns an object that can be used to configure a complex property of the complex type. /// If no property with the given name exists, then a new property will be added. @@ -205,6 +227,29 @@ public virtual PropertyBuilder Property(Expression buildAction) => (EntityTypeBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + Type propertyType, + string propertyName, + string complexTypeName, + Action buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyType, propertyName, complexTypeName, buildAction); + /// /// Returns an object that can be used to configure a complex property of the entity type. /// If the specified property is not already part of the model, it will be added. @@ -219,6 +264,28 @@ public virtual ComplexPropertyBuilder ComplexProperty( => new( Builder.ComplexProperty( Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + complexTypeName: null, + collection: false, + ConfigurationSource.Explicit)! + .Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// The name of the complex type. + /// An object that can be used to configure the complex property. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + Check.NotEmpty(complexTypeName, nameof(complexTypeName)), collection: false, ConfigurationSource.Explicit)! .Metadata); @@ -243,6 +310,29 @@ public virtual EntityTypeBuilder ComplexProperty( return this; } + /// + /// Configures a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// The name of the complex type. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression, complexTypeName)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. diff --git a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs index e0076e164fb..eb4ec7a65b9 100644 --- a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs @@ -181,6 +181,18 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, bool fromDataAnnotation = false); + /// + /// Marks a type as complex. All references to this type will be configured as complex properties. + /// + /// The type to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + IConventionModelBuilder? Complex( + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + bool fromDataAnnotation = false); + /// /// Indicates whether the given entity type name is ignored for the current configuration source. /// diff --git a/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs index 57bdd6d2a6e..d8187c8e2c2 100644 --- a/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ComplexPropertyDiscoveryConvention.cs @@ -56,7 +56,8 @@ private bool TryConfigureComplexProperty(MemberInfo? candidateMember, IConventio || typeBase.IsIgnored(candidateMember.Name) || typeBase.FindMember(candidateMember.Name) != null || (candidateMember is PropertyInfo propertyInfo && propertyInfo.GetIndexParameters().Length != 0) - || !Dependencies.MemberClassifier.IsCandidateComplexProperty(candidateMember, typeBase.Model, out var elementType)) + || !Dependencies.MemberClassifier.IsCandidateComplexProperty( + candidateMember, typeBase.Model, out var elementType, out var explicitlyConfigured)) { return false; } @@ -64,13 +65,14 @@ private bool TryConfigureComplexProperty(MemberInfo? candidateMember, IConventio var model = (Model)typeBase.Model; var targetClrType = (elementType ?? candidateMember.GetMemberType()).UnwrapNullableType(); if (typeBase.Model.Builder.IsIgnored(targetClrType) - || (typeBase is ComplexType complexType - && ((IReadOnlyComplexType)complexType).IsInDeclarationPath(targetClrType))) + || (typeBase is IReadOnlyComplexType complexType + && complexType.IsInDeclarationPath(targetClrType))) { return false; } - if (model.FindIsComplexConfigurationSource(targetClrType) == null) + if (!explicitlyConfigured + && model.FindIsComplexConfigurationSource(targetClrType) == null) { AddComplexCandidate(candidateMember, typeBase.Builder); return false; diff --git a/src/EFCore/Metadata/Conventions/ComplexTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ComplexTypeAttributeConvention.cs new file mode 100644 index 00000000000..9851812caea --- /dev/null +++ b/src/EFCore/Metadata/Conventions/ComplexTypeAttributeConvention.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A convention that configures types that have the . +/// +/// +/// See Model building conventions for more information and examples. +/// +public class ComplexTypeAttributeConvention : TypeAttributeConventionBase +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public ComplexTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + /// Called after an entity type is added to the model if it has an attribute. + /// + /// The builder for the entity type. + /// The attribute. + /// Additional information associated with convention execution. + protected override void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + ComplexTypeAttribute attribute, + IConventionContext context) + { + entityTypeBuilder.Metadata.Model.Builder.Complex(entityTypeBuilder.Metadata.ClrType); + + if (!entityTypeBuilder.Metadata.IsInModel) + { + context.StopProcessing(); + } + } +} diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 77432634a34..4a356d038d8 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -55,8 +55,10 @@ public virtual ConventionSet CreateConventionSet() var conventionSet = new ConventionSet(); conventionSet.Add(new ModelCleanupConvention(Dependencies)); + conventionSet.Add(new NotMappedTypeAttributeConvention(Dependencies)); conventionSet.Add(new OwnedAttributeConvention(Dependencies)); + conventionSet.Add(new ComplexTypeAttributeConvention(Dependencies)); conventionSet.Add(new KeylessAttributeConvention(Dependencies)); conventionSet.Add(new EntityTypeConfigurationAttributeConvention(Dependencies)); conventionSet.Add(new NotMappedMemberAttributeConvention(Dependencies)); @@ -73,8 +75,9 @@ public virtual ConventionSet CreateConventionSet() conventionSet.Add(new InversePropertyAttributeConvention(Dependencies)); conventionSet.Add(new DeleteBehaviorAttributeConvention(Dependencies)); conventionSet.Add(new NavigationBackingFieldAttributeConvention(Dependencies)); - conventionSet.Add(new NavigationEagerLoadingConvention(Dependencies)); conventionSet.Add(new RequiredNavigationAttributeConvention(Dependencies)); + + conventionSet.Add(new NavigationEagerLoadingConvention(Dependencies)); conventionSet.Add(new DbSetFindingConvention(Dependencies)); conventionSet.Add(new BaseTypeDiscoveryConvention(Dependencies)); conventionSet.Add(new ManyToManyJoinEntityTypeConvention(Dependencies)); diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index 525ebc0ec4c..5526c48e9e8 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -52,6 +52,21 @@ public void ProcessComplexPropertyAdded( complexType.Builder.Property(propertyInfo); } + + if (!complexType.ClrType.IsValueType) + { + return; + } + + foreach (var fieldInfo in complexType.GetRuntimeFields().Values) + { + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(fieldInfo, model)) + { + continue; + } + + complexType.Builder.Property(fieldInfo); + } } /// diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index 5cfd5a75057..3d1e87a6b6d 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -81,7 +81,7 @@ private IReadOnlyList FindRelationshipCandidates( continue; } - if (((Metadata.Internal.Model)entityType.Model).FindIsComplexConfigurationSource(targetClrType) != null) + if (((Model)entityType.Model).FindIsComplexConfigurationSource(targetClrType) != null) { continue; } diff --git a/src/EFCore/Metadata/IComplexType.cs b/src/EFCore/Metadata/IComplexType.cs index e49eec386bd..55d930d4d04 100644 --- a/src/EFCore/Metadata/IComplexType.cs +++ b/src/EFCore/Metadata/IComplexType.cs @@ -21,9 +21,4 @@ public interface IComplexType : IReadOnlyComplexType, ITypeBase /// IEntityType ITypeBase.FundamentalEntityType => (IEntityType)((IReadOnlyComplexType)this).FundamentalEntityType; - - /// - /// Gets the for the preferred constructor. - /// - InstantiationBinding? ConstructorBinding { get; } } diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 913e699fb55..3bfc0aad1a8 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -260,14 +260,15 @@ bool IsIgnored(string memberName) /// Adds a property to this type. /// /// The corresponding member on the type. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. [RequiresUnreferencedCode("Currently used only in tests")] - IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, bool collection = false, bool fromDataAnnotation = false) + IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, string? complexTypeName = null, bool collection = false, bool fromDataAnnotation = false) => AddComplexProperty( memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), - memberInfo, memberInfo.GetMemberType(), collection, fromDataAnnotation); + memberInfo, memberInfo.GetMemberType(), complexTypeName, collection, fromDataAnnotation); /// /// Adds a property to this type. @@ -284,6 +285,7 @@ bool IsIgnored(string memberName) /// The name of the property to add. /// The property type. /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. @@ -291,6 +293,7 @@ bool IsIgnored(string memberName) string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false, bool fromDataAnnotation = false); @@ -308,6 +311,7 @@ bool IsIgnored(string memberName) /// /// /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. @@ -316,6 +320,7 @@ bool IsIgnored(string memberName) [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo memberInfo, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false, bool fromDataAnnotation = false); @@ -325,6 +330,7 @@ bool IsIgnored(string memberName) /// The name of the property to add. /// The property type. /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// Indicates whether the configuration was specified using a data annotation. /// The newly created property. @@ -332,6 +338,7 @@ bool IsIgnored(string memberName) string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false, bool fromDataAnnotation = false) { @@ -342,7 +349,7 @@ bool IsIgnored(string memberName) CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); } - return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, fromDataAnnotation); + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, complexTypeName, fromDataAnnotation); } /// diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index a4f96f176cd..0d334942a20 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -19,11 +19,6 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// new IEntityType? BaseType { get; } - /// - /// Gets the for the preferred constructor. - /// - InstantiationBinding? ConstructorBinding { get; } - /// /// Gets the for the preferred constructor when creating instances with only service /// properties initialized. diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index 8b5bc3034ef..8cec9ed78bb 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -227,12 +227,13 @@ IMutableProperty AddIndexerProperty( /// Adds a complex property to this type. /// /// The corresponding member on the class. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// The newly created property. [RequiresUnreferencedCode("Currently used only in tests")] - IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, bool collection = false) + IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, string? complexTypeName = null, bool collection = false) => AddComplexProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), - collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), collection); + collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), complexTypeName, collection); /// /// Adds a complex property to this type. @@ -248,12 +249,14 @@ IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, bool collectio /// The name of the property to add. /// The property type. /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// The newly created property. IMutableComplexProperty AddComplexProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false); /// @@ -270,6 +273,7 @@ IMutableComplexProperty AddComplexProperty( /// /// /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// The newly created property. IMutableComplexProperty AddComplexProperty( @@ -277,6 +281,7 @@ IMutableComplexProperty AddComplexProperty( [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo memberInfo, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false); /// @@ -285,12 +290,14 @@ IMutableComplexProperty AddComplexProperty( /// The name of the property to add. /// The property type. /// The type of value the property will hold. + /// The name of the complex type. /// Indicates whether the property represents a collection. /// The newly created property. IMutableComplexProperty AddComplexIndexerProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + string? complexTypeName = null, bool collection = false) { var indexerPropertyInfo = FindIndexerPropertyInfo(); @@ -300,7 +307,7 @@ IMutableComplexProperty AddComplexIndexerProperty( CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); } - return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, collection); + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, complexTypeName, collection); } /// diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index 82fb8c95797..8d71a435a85 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -22,6 +22,11 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable new IEntityType FundamentalEntityType => (IEntityType)this; + /// + /// Gets the for the preferred constructor. + /// + InstantiationBinding? ConstructorBinding { get; } + /// /// Gets a property on the given type. Returns if no property is found. /// diff --git a/src/EFCore/Metadata/Internal/ComplexProperty.cs b/src/EFCore/Metadata/Internal/ComplexProperty.cs index a577618cb47..183703ac1ae 100644 --- a/src/EFCore/Metadata/Internal/ComplexProperty.cs +++ b/src/EFCore/Metadata/Internal/ComplexProperty.cs @@ -30,6 +30,7 @@ public ComplexProperty( PropertyInfo? propertyInfo, FieldInfo? fieldInfo, TypeBase declaringType, + string? targetTypeName, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, bool collection, ConfigurationSource configurationSource) @@ -39,7 +40,7 @@ public ComplexProperty( DeclaringType = declaringType; IsCollection = collection; ComplexType = new ComplexType( - declaringType.GetOwnedName(targetType.ShortDisplayName(), name), + targetTypeName ?? declaringType.GetOwnedName(targetType.ShortDisplayName(), name), targetType, this, configurationSource); _builder = new InternalComplexPropertyBuilder(this, declaringType.Model.Builder); } @@ -215,7 +216,7 @@ public static bool IsCompatible( if (shouldThrow) { throw new InvalidOperationException( - CoreStrings.NavigationCollectionWrongClrType( + CoreStrings.ComplexCollectionWrongClrType( propertyName, sourceType.DisplayName(), memberInfo.GetMemberType().ShortDisplayName(), @@ -231,7 +232,7 @@ public static bool IsCompatible( if (shouldThrow) { throw new InvalidOperationException( - CoreStrings.NavigationSingleWrongClrType( + CoreStrings.ComplexPropertyWrongClrType( propertyName, sourceType.DisplayName(), memberInfo.GetMemberType().ShortDisplayName(), diff --git a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs index b1a324e145d..11c54816097 100644 --- a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs +++ b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs @@ -94,6 +94,7 @@ public ComplexPropertySnapshot( ComplexProperty.ClrType, ComplexProperty.Name, ComplexProperty.GetIdentifyingMemberInfo(), + ComplexType.Name, ComplexType.ClrType, ComplexProperty.IsCollection, configurationSource); diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs index 1eb9ad22ca3..b9411a31c75 100644 --- a/src/EFCore/Metadata/Internal/ComplexType.cs +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -64,6 +63,8 @@ public ComplexType( ComplexProperty = property; _builder = new InternalComplexTypeBuilder(this, property.DeclaringType.Model.Builder); + + Model.AddComplexType(this); } /// @@ -131,6 +132,7 @@ public override bool IsInModel public virtual void SetRemovedFromModel() { _builder = null; + Model.RemoveComplexType(this); BaseType?.DirectlyDerivedTypes.Remove(this); } @@ -496,7 +498,7 @@ public virtual IReadOnlyList ValueGeneratingProperties /// 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 InstantiationBinding? ConstructorBinding + public override InstantiationBinding? ConstructorBinding { get => IsReadOnly && !ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 098ca73b7a2..fd8cdc105b2 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2994,7 +2994,7 @@ public virtual bool IsImplicitlyCreatedJoinEntityType /// 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 InstantiationBinding? ConstructorBinding + public override InstantiationBinding? ConstructorBinding { get => IsReadOnly && !ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs index d9fea6c551d..d6d3dd2440c 100644 --- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs @@ -48,7 +48,8 @@ public interface IMemberClassifier /// 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 IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, out Type? elementType); + bool IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, + out Type? elementType, out bool explicitlyConfigured); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 9531edcdb58..9d9f0391cf1 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -453,6 +453,37 @@ public virtual bool CanHaveEntity( private bool IsOwned(in TypeIdentity type) => type.Type != null && Metadata.IsOwned(type.Type); + /// + /// 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 InternalModelBuilder Complex(Type type, ConfigurationSource configurationSource) + { + var existingComplexConfiguration = Metadata.FindIsComplexConfigurationSource(type); + if (existingComplexConfiguration == null) + { + Metadata.AddComplex(type, configurationSource); + + foreach (var existingEntityType in Metadata.FindEntityTypes(type).ToList()) + { + Metadata.Builder.HasNoEntityType(existingEntityType, ConfigurationSource.Convention); + } + + var properties = Metadata.FindProperties(type); + if (properties != null) + { + foreach (var property in properties) + { + property.DeclaringType.Builder.RemoveProperty(property, ConfigurationSource.Convention); + } + } + } + + return this; + } + /// /// 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 @@ -899,6 +930,16 @@ IConventionModel IConventionModelBuilder.Metadata bool fromDataAnnotation) => Owned(type, 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. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.Complex(Type type, bool fromDataAnnotation) + => Complex(type, 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 @@ -1072,5 +1113,4 @@ bool IConventionModelBuilder.CanSetChangeTrackingStrategy(ChangeTrackingStrategy [DebuggerStepThrough] bool IConventionModelBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); -} + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);} diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index d133d77ae4f..5bc92880b93 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -836,7 +836,7 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); } - return ComplexProperty(propertyType, propertyName, indexerPropertyInfo, complexType, collection, configurationSource); + return ComplexProperty(propertyType, propertyName, indexerPropertyInfo, complexTypeName: null, complexType, collection, configurationSource); } /// @@ -847,10 +847,12 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo /// public virtual InternalComplexPropertyBuilder? ComplexProperty( MemberInfo memberInfo, + string? complexTypeName, bool? collection, ConfigurationSource? configurationSource) => ComplexProperty( - memberInfo.GetMemberType(), memberInfo.Name, memberInfo, complexType: null, collection, configurationSource); + memberInfo.GetMemberType(), memberInfo.Name, memberInfo, complexTypeName, + complexType: null, collection, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -861,10 +863,11 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo public virtual InternalComplexPropertyBuilder? ComplexProperty( [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, string propertyName, + string? complexTypeName, bool? collection, ConfigurationSource? configurationSource) => ComplexProperty( - propertyType, propertyName, memberInfo: null, complexType: null, collection, configurationSource); + propertyType, propertyName, memberInfo: null, complexTypeName, complexType: null, collection, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -876,11 +879,12 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, string propertyName, MemberInfo? memberInfo, + string? complexTypeName, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, bool? collection, ConfigurationSource? configurationSource) { - var entityType = Metadata; + var typeBase = Metadata; List? propertiesToDetach = null; var existingComplexProperty = Metadata.FindComplexProperty(propertyName); if (existingComplexProperty != null) @@ -892,7 +896,7 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo Metadata.RemoveIgnored(propertyName); } - entityType = (EntityType)existingComplexProperty.DeclaringType; + typeBase = (EntityType)existingComplexProperty.DeclaringType; } var existingComplexType = existingComplexProperty.ComplexType; @@ -976,27 +980,9 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo } var model = Metadata.Model; - InternalComplexPropertyBuilder builder; + ComplexProperty complexProperty; using (model.DelayConventions()) { - var existingComplexConfiguration = model.AddComplex(complexType!, configurationSource.Value); - if (existingComplexConfiguration == null) - { - foreach (var existingEntityType in model.FindEntityTypes(complexType!).ToList()) - { - model.Builder.HasNoEntityType(existingEntityType, ConfigurationSource.Convention); - } - - var properties = model.FindProperties(complexType!); - if (properties != null) - { - foreach (var property in properties) - { - property.DeclaringType.Builder.RemoveProperty(property, ConfigurationSource.Convention); - } - } - } - var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); if (existingComplexProperty == null) { @@ -1005,8 +991,10 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo RemoveMembersInHierarchy(propertyName, configurationSource.Value); } - builder = entityType.AddComplexProperty( - propertyName, propertyType, memberInfo, complexType!, collection.Value, configurationSource.Value)!.Builder; + model.Builder.Complex(complexType!, configurationSource.Value); + + complexProperty = typeBase.AddComplexProperty( + propertyName, propertyType, memberInfo, complexTypeName, complexType!, collection.Value, configurationSource.Value)!; if (detachedProperties != null) { @@ -1017,8 +1005,8 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo } } - return builder.Metadata.IsInModel - ? builder + return complexProperty.IsInModel + ? complexProperty.Builder : Metadata.FindComplexProperty(propertyName)?.Builder; } @@ -1450,6 +1438,7 @@ bool IConventionTypeBaseBuilder.CanRemoveProperty(IConventionProperty property, propertyType, propertyName, memberInfo: null, + complexTypeName: null, complexType: complexType, collection: null, configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); @@ -1467,6 +1456,7 @@ bool IConventionTypeBaseBuilder.CanRemoveProperty(IConventionProperty property, propertyType: memberInfo.GetMemberType(), propertyName: memberInfo.Name, memberInfo: memberInfo, + complexTypeName: null, complexType: complexType, collection: null, configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs index 7d1e3867886..4591b604af6 100644 --- a/src/EFCore/Metadata/Internal/MemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs @@ -185,8 +185,10 @@ public virtual bool IsCandidatePrimitiveProperty(MemberInfo memberInfo, IConvent /// 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 IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, out Type? elementType) + public virtual bool IsCandidateComplexProperty(MemberInfo memberInfo, IConventionModel model, + out Type? elementType, out bool explicitlyConfigured) { + explicitlyConfigured = false; elementType = null; if (!memberInfo.IsCandidateProperty()) { @@ -195,24 +197,26 @@ public virtual bool IsCandidateComplexProperty(MemberInfo memberInfo, IConventio var targetType = memberInfo.GetMemberType(); if (targetType.TryGetSequenceType() is Type sequenceType - && IsCandidateComplexType(sequenceType, model)) + && IsCandidateComplexType(sequenceType, model, out explicitlyConfigured)) { elementType = sequenceType; return true; } - return IsCandidateComplexType(targetType, model); + return IsCandidateComplexType(targetType, model, out explicitlyConfigured); } - private static bool IsCandidateComplexType(Type targetType, IConventionModel model) + private static bool IsCandidateComplexType(Type targetType, IConventionModel model, out bool explicitlyConfigured) { if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { + explicitlyConfigured = false; return false; } var configurationType = ((Model)model).Configuration?.GetConfigurationType(targetType); + explicitlyConfigured = configurationType != null; return configurationType == TypeConfigurationType.ComplexType || configurationType == null; } diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 36e8d7be95c..2d149785bd5 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -30,7 +30,8 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IRu private readonly ConcurrentDictionary _clrTypeNameMap = new(); private readonly Dictionary _ignoredTypeNames = new(StringComparer.Ordinal); private Dictionary? _ownedTypes; - private Dictionary? _complexTypes; + private Dictionary? _configuredComplexTypes; + private SortedDictionary? _complexTypes; private Dictionary>? _propertiesByType; private readonly Dictionary Types)> _sharedTypes = @@ -738,7 +739,7 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) /// public virtual ConfigurationSource? FindIsComplexConfigurationSource(Type type) { - if (_complexTypes == null) + if (_configuredComplexTypes == null) { return null; } @@ -746,7 +747,7 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) var currentType = type; while (currentType != null) { - if (_complexTypes.TryGetValue(currentType, out var configurationSource)) + if (_configuredComplexTypes.TryGetValue(currentType, out var configurationSource)) { return configurationSource; } @@ -766,15 +767,15 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) public virtual ConfigurationSource? AddComplex(Type type, ConfigurationSource configurationSource) { EnsureMutable(); - _complexTypes ??= new Dictionary(); - if (_complexTypes.TryGetValue(type, out var oldConfigurationSource)) + _configuredComplexTypes ??= new Dictionary(); + if (_configuredComplexTypes.TryGetValue(type, out var oldConfigurationSource)) { - _complexTypes[type] = configurationSource.Max(oldConfigurationSource); + _configuredComplexTypes[type] = configurationSource.Max(oldConfigurationSource); return oldConfigurationSource; } - _complexTypes.Add(type, configurationSource); + _configuredComplexTypes.Add(type, configurationSource); return null; } @@ -784,27 +785,38 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) /// 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 Type? RemoveComplex(Type type) + public virtual ComplexType? FindComplexType(string name) + => _complexTypes?.GetValueOrDefault(name); + + /// + /// 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 void AddComplexType(ComplexType complexType) { EnsureMutable(); - if (_complexTypes == null) - { - return null; - } + _complexTypes ??= new(StringComparer.Ordinal); - var currentType = type; - while (currentType != null) + if (!_complexTypes.TryAdd(complexType.Name, complexType)) { - if (_complexTypes.Remove(type)) - { - return type; - } - - currentType = currentType.BaseType; + throw new InvalidOperationException(CoreStrings.DuplicateComplexType(complexType.Name)); } + } - 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 void RemoveComplexType(ComplexType complexType) + { + EnsureMutable(); + + _complexTypes?.Remove(complexType.Name); } /// diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs index 16e9e7aae1f..54855ec8506 100644 --- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs +++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs @@ -27,7 +27,10 @@ public class ModelConfiguration /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsEmpty() - => _properties.Count == 0 && _ignoredTypes.Count == 0 && _typeMappings.Count == 0; + => _properties.Count == 0 + && _ignoredTypes.Count == 0 + && _typeMappings.Count == 0 + && _complexProperties.Count == 0; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index d9c0c912926..0dead57f78d 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -794,6 +794,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields() name, propertyType, memberInfo: null, + complexTypeName: null, targetType, collection, configurationSource); @@ -814,7 +815,8 @@ public virtual IReadOnlyDictionary GetRuntimeFields() memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo, - memberInfo.GetMemberType(), + complexTypeName: null, + collection ? memberInfo.GetMemberType().TryGetSequenceType()! : memberInfo.GetMemberType(), collection, configurationSource); @@ -857,6 +859,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields() string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, MemberInfo? memberInfo, + string? complexTypeName, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, bool collection, ConfigurationSource configurationSource) @@ -921,7 +924,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields() var property = new ComplexProperty( name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, - targetType, collection, configurationSource); + complexTypeName, targetType, collection, configurationSource); _complexProperties.Add(property.Name, property); @@ -1287,6 +1290,14 @@ public virtual bool IsIgnored(string name) private string DisplayName() => ((IReadOnlyTypeBase)this).DisplayName(); + /// + /// 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 abstract InstantiationBinding? ConstructorBinding { get; set; } + /// /// 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 @@ -1718,8 +1729,9 @@ IMutableComplexProperty IMutableTypeBase.AddComplexProperty(string name, bool co /// [DebuggerStepThrough] IMutableComplexProperty IMutableTypeBase.AddComplexProperty( - string name, Type propertyType, Type targetType, bool collection) - => AddComplexProperty(name, propertyType, targetType, collection, ConfigurationSource.Explicit)!; + string name, Type propertyType, Type targetType, string? complexTypeName, bool collection) + => AddComplexProperty(name, propertyType, memberInfo: null, complexTypeName, targetType, collection, + ConfigurationSource.Explicit)!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1729,8 +1741,8 @@ IMutableComplexProperty IMutableTypeBase.AddComplexProperty( /// [DebuggerStepThrough] IConventionComplexProperty? IConventionTypeBase.AddComplexProperty( - string name, Type propertyType, Type targetType, bool collection, bool fromDataAnnotation) - => AddComplexProperty(name, propertyType, targetType, collection, + string name, Type propertyType, Type targetType, string? complexTypeName, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, memberInfo: null, complexTypeName, targetType, collection, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; /// @@ -1741,8 +1753,8 @@ IMutableComplexProperty IMutableTypeBase.AddComplexProperty( /// [DebuggerStepThrough] IMutableComplexProperty IMutableTypeBase.AddComplexProperty( - string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection) - => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, ConfigurationSource.Explicit)!; + string name, Type propertyType, MemberInfo memberInfo, Type targetType, string? complexTypeName, bool collection) + => AddComplexProperty(name, propertyType, memberInfo, complexTypeName, targetType, collection, ConfigurationSource.Explicit)!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1752,8 +1764,8 @@ IMutableComplexProperty IMutableTypeBase.AddComplexProperty( /// [DebuggerStepThrough] IConventionComplexProperty? IConventionTypeBase.AddComplexProperty( - string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection, bool fromDataAnnotation) - => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, + string name, Type propertyType, MemberInfo memberInfo, Type targetType, string? complexTypeName, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, memberInfo, complexTypeName, targetType, collection, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; /// diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index 81b5cf5f849..3151c62bf18 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -105,7 +105,7 @@ public override IEnumerable FindMembersInHierarchy(string n /// /// Gets or sets the for the preferred constructor. /// - public virtual InstantiationBinding? ConstructorBinding + public override InstantiationBinding? ConstructorBinding { get => !ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 8d66ad16265..326ca9a959d 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -739,7 +739,7 @@ private IEnumerable GetTriggers() /// /// Gets or sets the for the preferred constructor. /// - public virtual InstantiationBinding? ConstructorBinding + public override InstantiationBinding? ConstructorBinding { get => !base.ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index 7282fa7bcee..7802b06ebe4 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -483,6 +483,11 @@ private IEnumerable FindDerivedComplexProperties(string /// public abstract IEnumerable FindMembersInHierarchy(string name); + /// + /// Gets or sets the for the preferred constructor. + /// + public abstract InstantiationBinding? ConstructorBinding { get; set; } + /// /// 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/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 12bd1a04d44..0fa7510a1eb 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -464,6 +464,30 @@ public static string ComplexCollectionWrongClrType(object? property, object? typ GetString("ComplexCollectionWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), property, type, clrType, targetType); + /// + /// Adding the collection complex property '{type}.{property}' isn't supported. + /// + public static string ComplexPropertyCollection(object? type, object? property) + => string.Format( + GetString("ComplexPropertyCollection", nameof(type), nameof(property)), + type, property); + + /// + /// Adding the complex property '{type}.{property}' as an indexer property isn't supported. + /// + public static string ComplexPropertyIndexer(object? type, object? property) + => string.Format( + GetString("ComplexPropertyIndexer", nameof(type), nameof(property)), + type, property); + + /// + /// Configuring the complex property '{type}.{property}' in shadow state isn't supported. + /// + public static string ComplexPropertyShadow(object? type, object? property) + => string.Format( + GetString("ComplexPropertyShadow", nameof(type), nameof(property)), + type, property); + /// /// The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. /// @@ -502,6 +526,14 @@ public static string ConflictingBackingFields(object? property, object? entityTy GetString("ConflictingBackingFields", "0_property", "1_entityType", nameof(field1), nameof(field2)), property, entityType, field1, field2); + /// + /// The member '{type}.{property}' cannot use field '{field}' because it is already used by '{conflictingType}.{conflictingProperty}'. + /// + public static string ConflictingFieldProperty(object? type, object? property, object? field, object? conflictingType, object? conflictingProperty) + => string.Format( + GetString("ConflictingFieldProperty", nameof(type), nameof(property), nameof(field), nameof(conflictingType), nameof(conflictingProperty)), + type, property, field, conflictingType, conflictingProperty); + /// /// There are multiple [ForeignKey] attributes which are pointing to same set of properties '{propertyList}' on entity type '{entityType}' and targeting the principal entity type '{principalEntityType}'. /// @@ -764,6 +796,14 @@ public static string DuplicateAnnotation(object? annotation, object? annotatable GetString("DuplicateAnnotation", nameof(annotation), nameof(annotatable)), annotation, annotatable); + /// + /// The complex type '{complexType}' cannot be added to the model because a complex type with the same name already exists. + /// + public static string DuplicateComplexType(object? complexType) + => string.Format( + GetString("DuplicateComplexType", nameof(complexType)), + complexType); + /// /// The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique discriminator value. /// @@ -852,6 +892,14 @@ public static string DuplicateTrigger(object? trigger, object? entityType, objec GetString("DuplicateTrigger", nameof(trigger), nameof(entityType), nameof(conflictingEntityType)), trigger, entityType, conflictingEntityType); + /// + /// Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model. + /// + public static string EmptyComplexType(object? complexType) + => string.Format( + GetString("EmptyComplexType", nameof(complexType)), + complexType); + /// /// Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. /// @@ -1276,7 +1324,7 @@ public static string InvalidAlternateKeyValue(object? entityType, object? keyPro entityType, keyProperty); /// - /// The specified type '{type}' must be a non-interface type with a public constructor to be used as an entity type. + /// The specified type '{type}' must be a non-interface type with a public constructor to be used as a complex type. /// public static string InvalidComplexType(object? type) => string.Format( @@ -1777,6 +1825,14 @@ public static string NavigationNotAddedAdHoc(object? entityType, object? navigat GetString("NavigationNotAddedAdHoc", nameof(entityType), nameof(navigation), nameof(propertyType)), entityType, navigation, propertyType); + /// + /// Unable to configure navigation '{complexType}.{navigation}' of type '{propertyType}' as complex types don't support navigations. Ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. + /// + public static string NavigationNotAddedComplexType(object? complexType, object? navigation, object? propertyType) + => string.Format( + GetString("NavigationNotAddedComplexType", nameof(complexType), nameof(navigation), nameof(propertyType)), + complexType, navigation, propertyType); + /// /// The navigation '{navigation}' cannot be added to the entity type '{entityType}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index a3871f1bb03..9fd655d1119 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -282,6 +282,15 @@ The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + + Adding the collection complex property '{type}.{property}' isn't supported. + + + Adding the complex property '{type}.{property}' as an indexer property isn't supported. + + + Configuring the complex property '{type}.{property}' in shadow state isn't supported. + The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. @@ -297,6 +306,9 @@ Property '{1_entityType}.{0_property}' matches both '{field1}' and '{field2}' by convention. Explicitly specify the backing field to use with 'HasField' in 'OnModelCreating'. + + The member '{type}.{property}' cannot use field '{field}' because it is already used by '{conflictingType}.{conflictingProperty}'. + There are multiple [ForeignKey] attributes which are pointing to same set of properties '{propertyList}' on entity type '{entityType}' and targeting the principal entity type '{principalEntityType}'. @@ -399,6 +411,9 @@ The annotation '{annotation}' cannot be added because an annotation with the same name already exists on the object {annotatable} + + The complex type '{complexType}' cannot be added to the model because a complex type with the same name already exists. + The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique discriminator value. @@ -432,6 +447,9 @@ The trigger '{trigger}' cannot be added to the entity type '{entityType}' because another trigger with the same name already exists on entity type '{conflictingEntityType}'. + + Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model. + Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query. @@ -1095,6 +1113,9 @@ The property '{entityType}.{navigation}' of type '{propertyType}' appears to be a navigation to another entity type. Navigations are not supported when using 'SqlQuery". Either include this type in the model and use 'FromSql' for the query, or ignore this property using the '[NotMapped]' attribute. + + Unable to configure navigation '{complexType}.{navigation}' of type '{propertyType}' as complex types don't support navigations. Ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. + The navigation '{navigation}' cannot be added to the entity type '{entityType}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. diff --git a/src/Shared/MemberInfoExtensions.cs b/src/Shared/MemberInfoExtensions.cs index 09dc3082894..06564f5c027 100644 --- a/src/Shared/MemberInfoExtensions.cs +++ b/src/Shared/MemberInfoExtensions.cs @@ -6,7 +6,6 @@ namespace System.Reflection; internal static class EntityFrameworkMemberInfoExtensions - { public static Type GetMemberType(this MemberInfo memberInfo) => (memberInfo as PropertyInfo)?.PropertyType ?? ((FieldInfo)memberInfo).FieldType; diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index 08af7fcd587..5085c43a96c 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -255,6 +255,7 @@ public override void Properties_can_have_provider_type_set_for_type() modelBuilder .Ignore() + .Ignore() .Entity( b => { diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index cb851c8f85f..0568c8bdcca 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -5337,6 +5337,83 @@ public virtual void SQLServer_property_legacy_identity_seed_int_annotation() #endregion + #region Complex types + + [ConditionalFact] + public virtual void Complex_properties_are_stored_in_snapshot() + => Test( + builder => + { + builder.Entity( + b => + { + b.ComplexProperty(eo => eo.EntityWithTwoProperties, eb => + { + eb.Property(e => e.AlternateId).HasColumnOrder(1); + eb.ComplexProperty(e => e.EntityWithStringKey); + }); + }); + }, + AddBoilerPlate( + GetHeading() + +""" + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.ComplexProperty>("EntityWithTwoProperties", "Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties", b1 => + { + b1.Property("AlternateId") + .HasColumnType("int") + .HasColumnOrder(1); + + b1.Property("Id") + .HasColumnType("int"); + + b1.ComplexProperty>("EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 => + { + b2.Property("Id") + .HasColumnType("nvarchar(max)"); + }); + }); + + b.HasKey("Id"); + + b.ToTable("EntityWithOneProperty", "DefaultSchema"); + }); +""", usingCollections: true), + o => + { + var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty)); + Assert.Equal(nameof(EntityWithOneProperty), entityWithOneProperty.GetTableName()); + + var complexProperty = entityWithOneProperty.FindComplexProperty(nameof(EntityWithOneProperty.EntityWithTwoProperties)); + Assert.False(complexProperty.IsCollection); + Assert.True(complexProperty.IsNullable); + var complexType = complexProperty.ComplexType; + Assert.Equal("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties", complexType.Name); + Assert.Equal("EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties", complexType.DisplayName()); + Assert.Equal(nameof(EntityWithOneProperty), complexType.GetTableName()); + var alternateIdProperty = complexType.FindProperty(nameof(EntityWithTwoProperties.AlternateId)); + Assert.Equal(1, alternateIdProperty.GetColumnOrder()); + + var nestedComplexProperty = complexType.FindComplexProperty(nameof(EntityWithTwoProperties.EntityWithStringKey)); + Assert.False(nestedComplexProperty.IsCollection); + Assert.True(nestedComplexProperty.IsNullable); + var nestedComplexType = nestedComplexProperty.ComplexType; + Assert.Equal("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", nestedComplexType.Name); + Assert.Equal("EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", nestedComplexType.DisplayName()); + Assert.Equal(nameof(EntityWithOneProperty), nestedComplexType.GetTableName()); + var nestedIdProperty = nestedComplexType.FindProperty(nameof(EntityWithStringKey.Id)); + Assert.True(nestedIdProperty.IsNullable); + }); + + #endregion + #region HasKey [ConditionalFact] @@ -7462,11 +7539,14 @@ protected virtual ICollection GetReferences() BuildReference.ByName("NetTopologySuite") }; - protected virtual string AddBoilerPlate(string code, bool usingSystem = false) + protected virtual string AddBoilerPlate(string code, bool usingSystem = false, bool usingCollections = false) => $$""" // {{(usingSystem ? @"using System; +" + : "")}}{{(usingCollections + ? @"using System.Collections.Generic; " : "")}}using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index f1adf106045..3f8a9a33f2f 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -4788,6 +4788,8 @@ private IRelationalModel CreateRelationalModel() principalBaseTable.Columns.Add("FlagsEnum2", flagsEnum2Column); var owned_NumberColumn = new Column("Owned_Number", "int", principalBaseTable); principalBaseTable.Columns.Add("Owned_Number", owned_NumberColumn); + var owned_Principal_AlternateIdColumn = new Column("Owned_Principal_AlternateId", "uniqueidentifier", principalBaseTable); + principalBaseTable.Columns.Add("Owned_Principal_AlternateId", owned_Principal_AlternateIdColumn); var owned_Principal_Enum1Column = new Column("Owned_Principal_Enum1", "int", principalBaseTable); principalBaseTable.Columns.Add("Owned_Principal_Enum1", owned_Principal_Enum1Column); var owned_Principal_Enum2Column = new Column("Owned_Principal_Enum2", "int", principalBaseTable) @@ -4809,52 +4811,6 @@ private IRelationalModel CreateRelationalModel() IsNullable = true }; principalBaseTable.Columns.Add("PrincipalBaseId", principalBaseIdColumn); - var principals_Deriveds_Enum1Column = new Column("Principals_Deriveds_Enum1", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Deriveds_Enum1", principals_Deriveds_Enum1Column); - var principals_Deriveds_Enum2Column = new Column("Principals_Deriveds_Enum2", "int", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Deriveds_Enum2", principals_Deriveds_Enum2Column); - var principals_Deriveds_FlagsEnum1Column = new Column("Principals_Deriveds_FlagsEnum1", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Deriveds_FlagsEnum1", principals_Deriveds_FlagsEnum1Column); - var principals_Deriveds_FlagsEnum2Column = new Column("Principals_Deriveds_FlagsEnum2", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Deriveds_FlagsEnum2", principals_Deriveds_FlagsEnum2Column); - var principals_Deriveds_IdColumn = new Column("Principals_Deriveds_Id", "bigint", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Deriveds_Id", principals_Deriveds_IdColumn); - var principals_Deriveds_Owned_DetailsColumn = new Column("Principals_Deriveds_Owned_Details", "nvarchar(max)", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Deriveds_Owned_Details", principals_Deriveds_Owned_DetailsColumn); - var principals_Deriveds_Owned_NumberColumn = new Column("Principals_Deriveds_Owned_Number", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Deriveds_Owned_Number", principals_Deriveds_Owned_NumberColumn); - var principals_Enum1Column = new Column("Principals_Enum1", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Enum1", principals_Enum1Column); - var principals_Enum2Column = new Column("Principals_Enum2", "int", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Enum2", principals_Enum2Column); - var principals_FlagsEnum1Column = new Column("Principals_FlagsEnum1", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_FlagsEnum1", principals_FlagsEnum1Column); - var principals_FlagsEnum2Column = new Column("Principals_FlagsEnum2", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_FlagsEnum2", principals_FlagsEnum2Column); - var principals_IdColumn = new Column("Principals_Id", "bigint", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Id", principals_IdColumn); - var principals_Owned_DetailsColumn = new Column("Principals_Owned_Details", "nvarchar(max)", principalBaseTable) - { - IsNullable = true - }; - principalBaseTable.Columns.Add("Principals_Owned_Details", principals_Owned_DetailsColumn); - var principals_Owned_NumberColumn = new Column("Principals_Owned_Number", "int", principalBaseTable); - principalBaseTable.Columns.Add("Principals_Owned_Number", principals_Owned_NumberColumn); var pK_PrincipalBase = new UniqueConstraint("PK_PrincipalBase", principalBaseTable, new[] { idColumn }); principalBaseTable.PrimaryKey = pK_PrincipalBase; var pK_PrincipalBaseUc = RelationalModel.GetKey(this, @@ -5101,6 +5057,7 @@ private IRelationalModel CreateRelationalModel() var principalBaseTableMapping1 = new TableMapping(principalBase0, principalBaseTable, true); principalBaseTable.AddTypeMapping(principalBaseTableMapping1, false); tableMappings1.Add(principalBaseTableMapping1); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Owned_Principal_AlternateId")!, principalBase0.FindProperty("AlternateId")!, principalBaseTableMapping1); RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Owned_Principal_Enum1")!, principalBase0.FindProperty("Enum1")!, principalBaseTableMapping1); RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Owned_Principal_Enum2")!, principalBase0.FindProperty("Enum2")!, principalBaseTableMapping1); RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Owned_Principal_FlagsEnum1")!, principalBase0.FindProperty("FlagsEnum1")!, principalBaseTableMapping1); @@ -5189,52 +5146,6 @@ private IRelationalModel CreateRelationalModel() RelationalModel.CreateStoredProcedureParameterMapping(flagsEnum1Parameter0, principalBase_UpdateUSproc0.FindParameter("FlagsEnum1")!, principalDerived.FindProperty("FlagsEnum1")!, principalBase_UpdateSprocMapping0); RelationalModel.CreateStoredProcedureParameterMapping(flagsEnum2Parameter0, principalBase_UpdateUSproc0.FindParameter("FlagsEnum2")!, principalDerived.FindProperty("FlagsEnum2")!, principalBase_UpdateSprocMapping0); RelationalModel.CreateStoredProcedureParameterMapping(principalBaseIdParameter0, principalBase_UpdateUSproc0.FindParameter("PrincipalBaseId")!, principalDerived.FindProperty("PrincipalBaseId")!, principalBase_UpdateSprocMapping0); - - var principalBase1 = principalDerived.FindComplexProperty("Principals")!.ComplexType; - - var tableMappings3 = new List(); - principalBase1.SetRuntimeAnnotation("Relational:TableMappings", tableMappings3); - var principalBaseTableMapping3 = new TableMapping(principalBase1, principalBaseTable, true); - principalBaseTable.AddTypeMapping(principalBaseTableMapping3, false); - tableMappings3.Add(principalBaseTableMapping3); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Enum1")!, principalBase1.FindProperty("Enum1")!, principalBaseTableMapping3); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Enum2")!, principalBase1.FindProperty("Enum2")!, principalBaseTableMapping3); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_FlagsEnum1")!, principalBase1.FindProperty("FlagsEnum1")!, principalBaseTableMapping3); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_FlagsEnum2")!, principalBase1.FindProperty("FlagsEnum2")!, principalBaseTableMapping3); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Id")!, principalBase1.FindProperty("Id")!, principalBaseTableMapping3); - - var principalBase2 = principalBase1.FindComplexProperty("Deriveds")!.ComplexType; - - var tableMappings4 = new List(); - principalBase2.SetRuntimeAnnotation("Relational:TableMappings", tableMappings4); - var principalBaseTableMapping4 = new TableMapping(principalBase2, principalBaseTable, true); - principalBaseTable.AddTypeMapping(principalBaseTableMapping4, false); - tableMappings4.Add(principalBaseTableMapping4); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_Enum1")!, principalBase2.FindProperty("Enum1")!, principalBaseTableMapping4); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_Enum2")!, principalBase2.FindProperty("Enum2")!, principalBaseTableMapping4); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_FlagsEnum1")!, principalBase2.FindProperty("FlagsEnum1")!, principalBaseTableMapping4); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_FlagsEnum2")!, principalBase2.FindProperty("FlagsEnum2")!, principalBaseTableMapping4); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_Id")!, principalBase2.FindProperty("Id")!, principalBaseTableMapping4); - - var ownedType0 = principalBase2.FindComplexProperty("Owned")!.ComplexType; - - var tableMappings5 = new List(); - ownedType0.SetRuntimeAnnotation("Relational:TableMappings", tableMappings5); - var principalBaseTableMapping5 = new TableMapping(ownedType0, principalBaseTable, true); - principalBaseTable.AddTypeMapping(principalBaseTableMapping5, false); - tableMappings5.Add(principalBaseTableMapping5); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_Owned_Details")!, ownedType0.FindProperty("Details")!, principalBaseTableMapping5); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Deriveds_Owned_Number")!, ownedType0.FindProperty("Number")!, principalBaseTableMapping5); - - var ownedType1 = principalBase1.FindComplexProperty("Owned")!.ComplexType; - - var tableMappings6 = new List(); - ownedType1.SetRuntimeAnnotation("Relational:TableMappings", tableMappings6); - var principalBaseTableMapping6 = new TableMapping(ownedType1, principalBaseTable, true); - principalBaseTable.AddTypeMapping(principalBaseTableMapping6, false); - tableMappings6.Add(principalBaseTableMapping6); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Owned_Details")!, ownedType1.FindProperty("Details")!, principalBaseTableMapping6); - RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Principals_Owned_Number")!, ownedType1.FindProperty("Number")!, principalBaseTableMapping6); var fK_PrincipalBase_PrincipalBase_PrincipalBaseId = new ForeignKeyConstraint( "FK_PrincipalBase_PrincipalBase_PrincipalBaseId", principalBaseTable, principalBaseTable, new[] { principalBaseIdColumn }, @@ -5415,6 +5326,13 @@ public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) nullable: true); var complexType = complexProperty.ComplexType; + var alternateId = complexType.AddProperty( + "AlternateId", + typeof(Guid), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("AlternateId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: new Guid("00000000-0000-0000-0000-000000000000")); + alternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + var enum1 = complexType.AddProperty( "Enum1", typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum), @@ -5557,7 +5475,6 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) """ // using System; -using System.Collections.Generic; using System.Reflection; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -5578,217 +5495,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalDerived>"); - PrincipalsComplexProperty.Create(runtimeEntityType); return runtimeEntityType; } - private static class PrincipalsComplexProperty - { - public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) - { - var complexProperty = declaringType.AddComplexProperty("Principals", - typeof(ICollection), - "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.Principals#PrincipalBase", - typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty("Principals", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true, - collection: true); - - var complexType = complexProperty.ComplexType; - var enum1 = complexType.AddProperty( - "Enum1", - typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AnEnum)0); - enum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var enum2 = complexType.AddProperty( - "Enum2", - typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum?), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - enum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var flagsEnum1 = complexType.AddProperty( - "FlagsEnum1", - typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); - flagsEnum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var flagsEnum2 = complexType.AddProperty( - "FlagsEnum2", - typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); - flagsEnum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var id = complexType.AddProperty( - "Id", - typeof(long?), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - DerivedsComplexProperty.Create(complexType); - OwnedComplexProperty.Create(complexType); - complexType.AddAnnotation("Relational:FunctionName", null); - complexType.AddAnnotation("Relational:Schema", null); - complexType.AddAnnotation("Relational:SqlQuery", "select * from PrincipalBase"); - complexType.AddAnnotation("Relational:TableName", "PrincipalBase"); - complexType.AddAnnotation("Relational:ViewName", null); - complexType.AddAnnotation("Relational:ViewSchema", null); - return complexProperty; - } - - private static class DerivedsComplexProperty - { - public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) - { - var complexProperty = declaringType.AddComplexProperty("Deriveds", - typeof(ICollection), - "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.Principals#PrincipalBase.Deriveds#PrincipalBase", - typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Deriveds", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true, - collection: true); - - var complexType = complexProperty.ComplexType; - var enum1 = complexType.AddProperty( - "Enum1", - typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AnEnum)0); - enum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var enum2 = complexType.AddProperty( - "Enum2", - typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum?), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - enum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var flagsEnum1 = complexType.AddProperty( - "FlagsEnum1", - typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); - flagsEnum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var flagsEnum2 = complexType.AddProperty( - "FlagsEnum2", - typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); - flagsEnum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var id = complexType.AddProperty( - "Id", - typeof(long?), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - OwnedComplexProperty.Create(complexType); - complexType.AddAnnotation("Relational:FunctionName", null); - complexType.AddAnnotation("Relational:Schema", null); - complexType.AddAnnotation("Relational:SqlQuery", "select * from PrincipalBase"); - complexType.AddAnnotation("Relational:TableName", "PrincipalBase"); - complexType.AddAnnotation("Relational:ViewName", null); - complexType.AddAnnotation("Relational:ViewSchema", null); - return complexProperty; - } - - private static class OwnedComplexProperty - { - public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) - { - var complexProperty = declaringType.AddComplexProperty("Owned", - typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), - "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.Principals#PrincipalBase.Deriveds#PrincipalBase.Owned#OwnedType", - typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Owned", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - - var complexType = complexProperty.ComplexType; - var details = complexType.AddProperty( - "Details", - typeof(string), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var number = complexType.AddProperty( - "Number", - typeof(int), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Number", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: 0); - number.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - complexType.AddAnnotation("Relational:FunctionName", null); - complexType.AddAnnotation("Relational:Schema", null); - complexType.AddAnnotation("Relational:SqlQuery", "select * from PrincipalBase"); - complexType.AddAnnotation("Relational:TableName", "PrincipalBase"); - complexType.AddAnnotation("Relational:ViewName", null); - complexType.AddAnnotation("Relational:ViewSchema", null); - return complexProperty; - } - } - } - - private static class OwnedComplexProperty - { - public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) - { - var complexProperty = declaringType.AddComplexProperty("Owned", - typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), - "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.Principals#PrincipalBase.Owned#OwnedType", - typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Owned", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - - var complexType = complexProperty.ComplexType; - var details = complexType.AddProperty( - "Details", - typeof(string), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - nullable: true); - details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - var number = complexType.AddProperty( - "Number", - typeof(int), - propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Number", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - sentinel: 0); - number.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); - - complexType.AddAnnotation("Relational:FunctionName", null); - complexType.AddAnnotation("Relational:Schema", null); - complexType.AddAnnotation("Relational:SqlQuery", "select * from PrincipalBase"); - complexType.AddAnnotation("Relational:TableName", "PrincipalBase"); - complexType.AddAnnotation("Relational:ViewName", null); - complexType.AddAnnotation("Relational:ViewSchema", null); - return complexProperty; - } - } - } - public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { runtimeEntityType.AddAnnotation("Relational:FunctionName", null); @@ -5906,7 +5615,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var nestedComplexType = complexType.FindComplexProperty(nameof(OwnedType.Principal)).ComplexType; - Assert.Equal(5, nestedComplexType.GetProperties().Count()); + Assert.Equal(6, nestedComplexType.GetProperties().Count()); var principalTable = StoreObjectIdentifier.Create(complexType, StoreObjectType.Table).Value; @@ -6173,8 +5882,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasComment("Dt") .IsRowVersion() .HasAnnotation("foo", "bar"); - - eb.ComplexProperty(o => o.Principal); + eb.Ignore(e => e.Context); + eb.ComplexProperty(o => o.Principal) + .Property(p => p.AlternateId); }); eb.ToTable("PrincipalBase"); @@ -6209,8 +5919,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity>>( eb => { - //eb.OwnsMany(typeof(OwnedType).FullName, "ManyOwned"); + //eb.ComplexCollection(typeof(OwnedType).Name, "ManyOwned"); eb.Ignore(p => p.Dependent); + eb.Ignore(p => p.Principals); eb.ToTable("PrincipalBase"); eb.ToFunction((string)null); }); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 3a5db515575..1d0022b6dcc 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -3,7 +3,6 @@ using System.Data; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using NameSpace1; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Metadata @@ -3116,7 +3115,7 @@ public void Can_use_relational_model_with_functions() public void Default_mappings_does_not_share_tableBase() { var modelBuilder = CreateConventionModelBuilder(); - modelBuilder.Entity().HasNoKey().ToTable((string)null); + modelBuilder.Entity().HasNoKey().ToTable((string)null); modelBuilder.Entity().HasNoKey().ToTable((string)null); var model = Finalize(modelBuilder); @@ -3127,7 +3126,7 @@ public void Default_mappings_does_not_share_tableBase() Assert.Empty(model.Functions); Assert.Empty(model.Queries); - var entityType1 = model.Model.FindEntityType(typeof(SameEntityType)); + var entityType1 = model.Model.FindEntityType(typeof(NameSpace1.SameEntityType)); var entityType2 = model.Model.FindEntityType(typeof(NameSpace2.SameEntityType)); var defaultMapping1 = Assert.Single(entityType1.GetDefaultMappings()); diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index a4daa2723b4..a888a5810ae 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -517,51 +517,6 @@ public virtual void Conflicting_sproc_rows_affected_return_and_result_column_thr .Message); } - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedReturnValue() - .HasRowsAffectedParameter())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedReturnValue() - .HasRowsAffectedResultColumn())) - .Message); - } - - [ConditionalFact] - public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), - Assert.Throws( - () => modelBuilder.Entity() - .UpdateUsingStoredProcedure( - s => s.HasRowsAffectedParameter() - .HasRowsAffectedResultColumn())) - .Message); - } - [ConditionalFact] public virtual void Duplicate_sproc_rows_affected_result_column_throws() { @@ -710,175 +665,6 @@ public virtual void Can_use_table_splitting() Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails"))); Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails"))); } - - [ConditionalFact] - public virtual void Sproc_overrides_update_when_renamed_in_TPH() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.HasDefaultSchema("mySchema"); - - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - modelBuilder.Entity().Property("RowVersion").IsRowVersion(); - modelBuilder.Entity() - .Ignore(s => s.SpecialBookLabel) - .InsertUsingStoredProcedure( - s => s.HasAnnotation("foo", "bar1") - .HasParameter(b => b.BookId) - .HasParameter("Discriminator") - .HasResultColumn( - b => b.Id, p => - { - var resultColumnBuilder = p.HasName("InsertId"); - var nonGenericBuilder = (IInfrastructure)resultColumnBuilder; - Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); - }) - .HasResultColumn("RowVersion")) - .UpdateUsingStoredProcedure( - s => s.HasAnnotation("foo", "bar2") - .HasOriginalValueParameter( - b => b.Id, p => - { - var parameterBuilder = p.HasName("UpdateId"); - var nonGenericBuilder = (IInfrastructure)parameterBuilder; - Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); - }) - .HasParameter(b => b.BookId) - .HasRowsAffectedParameter() - .HasOriginalValueParameter("RowVersion", p => p.HasName("OriginalRowVersion")) - .HasParameter("RowVersion", p => p.IsOutput())) - .DeleteUsingStoredProcedure( - s => s.HasAnnotation("foo", "bar3") - .HasOriginalValueParameter(b => b.Id, p => p.HasName("DeleteId")) - .HasRowsAffectedResultColumn() - .HasOriginalValueParameter("RowVersion")); - - modelBuilder.Entity() - .Ignore(s => s.BookLabel); - modelBuilder.Entity(); - - modelBuilder.Entity() - .InsertUsingStoredProcedure("Insert", s => { }) - .UpdateUsingStoredProcedure("Update", "dbo", s => { }) - .DeleteUsingStoredProcedure("BookLabel_Delete", s => { }); - - var model = modelBuilder.FinalizeModel(); - - var bookLabel = model.FindEntityType(typeof(BookLabel))!; - var insertSproc = bookLabel.GetInsertStoredProcedure()!; - Assert.Equal("Insert", insertSproc.Name); - Assert.Equal("mySchema", insertSproc.Schema); - Assert.Equal(new[] { "BookId", "Discriminator" }, insertSproc.Parameters.Select(p => p.PropertyName)); - Assert.Equal(new[] { "Id", "RowVersion" }, insertSproc.ResultColumns.Select(p => p.PropertyName)); - Assert.False(insertSproc.FindParameter("Discriminator")!.ForOriginalValue); - Assert.Null(insertSproc.FindParameter("Id")); - Assert.Null(insertSproc.FindResultColumn("Discriminator")); - Assert.False(insertSproc.FindResultColumn("Id")!.ForRowsAffected); - Assert.Equal("bar1", insertSproc["foo"]); - Assert.Same(bookLabel, insertSproc.EntityType); - - var updateSproc = bookLabel.GetUpdateStoredProcedure()!; - Assert.Equal("Update", updateSproc.Name); - Assert.Equal("dbo", updateSproc.Schema); - Assert.Equal(new[] { "Id", "BookId", null, "RowVersion", "RowVersion" }, updateSproc.Parameters.Select(p => p.PropertyName)); - Assert.Equal( - new[] { "UpdateId", "BookId", "RowsAffected", "OriginalRowVersion", "RowVersion" }, - updateSproc.Parameters.Select(p => p.Name)); - Assert.Empty(updateSproc.ResultColumns); - Assert.Equal("bar2", updateSproc["foo"]); - Assert.Same(bookLabel, updateSproc.EntityType); - - var rowsAffectedParameter = updateSproc.Parameters[2]; - Assert.Null(rowsAffectedParameter.ForOriginalValue); - Assert.True(rowsAffectedParameter.ForRowsAffected); - Assert.Equal(ParameterDirection.Output, rowsAffectedParameter.Direction); - Assert.Same(updateSproc, rowsAffectedParameter.StoredProcedure); - - var originalRowVersionParameter = updateSproc.Parameters[3]; - Assert.True(originalRowVersionParameter.ForOriginalValue); - Assert.False(originalRowVersionParameter.ForRowsAffected); - Assert.Equal(ParameterDirection.Input, originalRowVersionParameter.Direction); - Assert.Same(updateSproc, originalRowVersionParameter.StoredProcedure); - - var currentRowVersionParameter = updateSproc.Parameters[4]; - Assert.False(currentRowVersionParameter.ForOriginalValue); - Assert.False(currentRowVersionParameter.ForRowsAffected); - Assert.Equal(ParameterDirection.Output, currentRowVersionParameter.Direction); - Assert.Same(updateSproc, currentRowVersionParameter.StoredProcedure); - - var deleteSproc = bookLabel.GetDeleteStoredProcedure()!; - Assert.Equal("BookLabel_Delete", deleteSproc.Name); - Assert.Equal("mySchema", deleteSproc.Schema); - Assert.Equal(new[] { "Id", "RowVersion" }, deleteSproc.Parameters.Select(p => p.PropertyName)); - Assert.Equal(new[] { "DeleteId", "RowVersion_Original" }, deleteSproc.Parameters.Select(p => p.Name)); - Assert.Equal(new[] { "RowsAffected" }, deleteSproc.ResultColumns.Select(p => p.Name)); - Assert.Equal("bar3", deleteSproc["foo"]); - Assert.Same(bookLabel, deleteSproc.EntityType); - - var rowsAffectedResultColumn = deleteSproc.ResultColumns[0]; - Assert.True(rowsAffectedResultColumn.ForRowsAffected); - Assert.Same(deleteSproc, rowsAffectedResultColumn.StoredProcedure); - - var id = bookLabel.FindProperty(nameof(BookLabel.Id))!; - Assert.Single(id.GetOverrides()); - Assert.Equal( - "InsertId", - id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.InsertStoredProcedure)!.Value)); - Assert.Equal( - "Id", - id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.UpdateStoredProcedure)!.Value)); - Assert.Equal( - "Id", - id.GetColumnName(StoreObjectIdentifier.Create(bookLabel, StoreObjectType.DeleteStoredProcedure)!.Value)); - - var specialBookLabel = model.FindEntityType(typeof(SpecialBookLabel))!; - Assert.Same(insertSproc, specialBookLabel.GetInsertStoredProcedure()); - Assert.Same(updateSproc, specialBookLabel.GetUpdateStoredProcedure()); - Assert.Same(deleteSproc, specialBookLabel.GetDeleteStoredProcedure()); - - var extraSpecialBookLabel = model.FindEntityType(typeof(ExtraSpecialBookLabel))!; - Assert.Same(insertSproc, extraSpecialBookLabel.GetInsertStoredProcedure()); - Assert.Same(updateSproc, extraSpecialBookLabel.GetUpdateStoredProcedure()); - Assert.Same(deleteSproc, extraSpecialBookLabel.GetDeleteStoredProcedure()); - } - - [ConditionalFact] - public virtual void Sproc_overrides_are_removed_with_sproc() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - modelBuilder.Entity() - .Ignore(s => s.SpecialBookLabel) - .InsertUsingStoredProcedure( - s => s.HasParameter(b => b.BookId) - .HasResultColumn(b => b.Id, p => p.HasName("InsertId"))) - .UpdateUsingStoredProcedure( - s => s.HasParameter(b => b.Id, p => p.HasName("UpdateId")) - .HasParameter(b => b.BookId)) - .DeleteUsingStoredProcedure( - s => s.HasParameter(b => b.Id, p => p.HasName("DeleteId"))); - - var bookLabelEntityType = modelBuilder.Entity().Metadata; - - bookLabelEntityType.RemoveInsertStoredProcedure(); - bookLabelEntityType.RemoveUpdateStoredProcedure(); - bookLabelEntityType.RemoveDeleteStoredProcedure(); - - var model = modelBuilder.FinalizeModel(); - - var bookLabel = model.FindEntityType(typeof(BookLabel))!; - Assert.Null(bookLabel.GetInsertStoredProcedure()); - Assert.Null(bookLabel.GetUpdateStoredProcedure()); - Assert.Null(bookLabel.GetDeleteStoredProcedure()); - - var id = bookLabel.FindProperty(nameof(BookLabel.Id))!; - Assert.Empty(id.GetOverrides()); - } } public abstract class RelationalOneToManyTestBase : OneToManyTestBase diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 2b841396207..d426b9d838e 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -2369,7 +2369,6 @@ protected class Post public Author Author { get; set; } } - [ComplexType] protected class PostDetails { public int Id { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 3cf9f2bab6e..b1d6d437487 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -3,7 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query; -public class PrimitiveCollectionsQueryTestBase : QueryTestBase +public abstract class PrimitiveCollectionsQueryTestBase : QueryTestBase where TFixture : PrimitiveCollectionsQueryTestBase.PrimitiveCollectionsQueryFixtureBase, new() { protected PrimitiveCollectionsQueryTestBase(TFixture fixture) diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs index 25f01f86e3a..cff10b8897f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTestBase.cs @@ -58,7 +58,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con }); modelBuilder.Entity().Property("CategoryId").HasDefaultValue(1); - modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2);; + modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2); } } } diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index f2a0dbde446..f801e094818 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -16,7 +16,7 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class SqlServerGenericComplexTypeTestBase : SqlServerComplexType + public class SqlServerGenericComplexType: SqlServerComplexType { protected override TestModelBuilder CreateTestModelBuilder( TestHelpers testHelpers, diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs index 53998ac3ac3..37d8f51cea6 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -16,7 +16,7 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); } - public class SqlServerNonGenericComplexTypeTestBase : SqlServerComplexType + public class SqlServerNonGenericComplexType : SqlServerComplexType { protected override TestModelBuilder CreateTestModelBuilder( TestHelpers testHelpers, diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index be6de05160c..4a5b9142fa1 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -200,194 +200,10 @@ protected override TestModelBuilder CreateModelBuilder(Action(); - var indexBuilder = entityTypeBuilder - .HasIndex(ix => ix.Name) - .IsUnique(); - - var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; - var index = entityType.GetIndexes().Single(); - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsUnique(false); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsUnique(); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsClustered(); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsClustered(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(); - - Assert.Null(index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); - - Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); - - Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName(null); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.HasFilter("Foo"); - - Assert.Equal("Foo", index.GetFilter()); - - indexBuilder.HasFilter(null); - - Assert.Null(index.GetFilter()); - } - - [ConditionalFact] - public void Indexes_can_have_same_name_across_tables() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .HasIndex(e => e.Id, "Ix_Id") - .IsUnique(); - modelBuilder.Entity() - .HasIndex(e => e.CustomerId, "Ix_Id") - .IsUnique(); - - var model = modelBuilder.FinalizeModel(); - - var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); - Assert.Equal("Ix_Id", customerIndex.Name); - Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); - Assert.Equal( - "Ix_Id", customerIndex.GetDatabaseName( - StoreObjectIdentifier.Table("Customer"))); - - var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); - Assert.Equal("Ix_Id", detailsIndex.Name); - Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); - Assert.Equal( - "Ix_Id", detailsIndex.GetDatabaseName( - StoreObjectIdentifier.Table("CustomerDetails"))); - } - - [ConditionalFact] - public virtual void Can_set_store_type_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().HaveColumnType("smallint"); - c.Properties().HaveColumnType("nchar(max)"); - c.Properties(typeof(Nullable<>)).HavePrecision(2); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; - - Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); - Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); - Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); - var charm = entityType.FindProperty("Charm")!; - Assert.Equal("smallint", charm.GetColumnType()); - Assert.Null(charm.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); - var top = entityType.FindProperty("Top")!; - Assert.Equal("smallint", top.GetColumnType()); - Assert.Equal(2, top.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); - } - - [ConditionalFact] - public virtual void Can_set_fixed_length_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().AreFixedLength(false); - c.Properties().AreFixedLength(); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; - - Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); - Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); - Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); - Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); - Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); - Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); - Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); - } - - [ConditionalFact] - public virtual void Can_set_collation_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); - c.Properties().UseCollation("Latin1_General_BIN"); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks))!; - - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); - } - protected override TestModelBuilder CreateModelBuilder(Action? configure = null) => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); } + public abstract class SqlServerInheritance : RelationalInheritanceTestBase { [ConditionalFact] // #7240 diff --git a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs index 09e9b6c5f14..d687b8987c6 100644 --- a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs @@ -217,7 +217,7 @@ public void Does_non_key_SQL_Server_string_mapping_with_long_string(Type type, b Assert.Null(typeMapping.Size); Assert.True(typeMapping.IsUnicode); Assert.False(typeMapping.IsFixedLength); - object value = type == typeof(string) ? new string('X', 4001) : Enumerable.Range(1, 2000).ToList();; + object value = type == typeof(string) ? new string('X', 4001) : Enumerable.Range(1, 2000).ToList(); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", value).Size); Assert.Equal(type == typeof(string) ? null : typeof(int), typeMapping.ElementTypeMapping?.ClrType); } @@ -240,7 +240,7 @@ public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_str Assert.Equal(3, typeMapping.Size); Assert.True(typeMapping.IsUnicode); Assert.False(typeMapping.IsFixedLength); - object value = type == typeof(string) ? new string('X', 4001) : Enumerable.Range(1, 2000).ToList();; + object value = type == typeof(string) ? new string('X', 4001) : Enumerable.Range(1, 2000).ToList(); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", value).Size); Assert.Equal(type == typeof(string) ? null : typeof(int), typeMapping.ElementTypeMapping?.ClrType); } @@ -567,7 +567,7 @@ public void Does_non_key_SQL_Server_string_mapping_with_long_string_ansi(Type ty Assert.Null(typeMapping.Size); Assert.False(typeMapping.IsUnicode); Assert.False(typeMapping.IsFixedLength); - object value = type == typeof(string) ? new string('X', 8001) : Enumerable.Range(1, 6000).ToList();; + object value = type == typeof(string) ? new string('X', 8001) : Enumerable.Range(1, 6000).ToList(); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", value).Size); Assert.Equal(type == typeof(string) ? null : typeof(int), typeMapping.ElementTypeMapping?.ClrType); } @@ -586,7 +586,7 @@ public void Does_non_key_SQL_Server_string_mapping_with_max_length_with_long_str Assert.Equal(3, typeMapping.Size); Assert.False(typeMapping.IsUnicode); Assert.False(typeMapping.IsFixedLength); - object value = type == typeof(string) ? new string('X', 8001) : Enumerable.Range(1, 6000).ToList();; + object value = type == typeof(string) ? new string('X', 8001) : Enumerable.Range(1, 6000).ToList(); Assert.Equal(-1, typeMapping.CreateParameter(new TestCommand(), "Name", value).Size); Assert.Equal(type == typeof(string) ? null : typeof(int), typeMapping.ElementTypeMapping?.ClrType); } diff --git a/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTestBase.cs b/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTestBase.cs index 6764c2e600c..b68445bf2dc 100644 --- a/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTestBase.cs +++ b/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTestBase.cs @@ -96,7 +96,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con }); modelBuilder.Entity().Property("CategoryId").HasDefaultValue(1); - modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2);; + modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2); } } } diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index 56b43adee86..2c564c70a19 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -120,6 +120,8 @@ protected override void Initialize() nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(string) }), typeof(ComplexPropertyBuilder).GetMethod( nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(Type), typeof(string) }), + typeof(ComplexPropertyBuilder).GetMethod( + nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(Type), typeof(string) , typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( @@ -156,6 +158,7 @@ protected override void Initialize() typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.SetAnnotation)), typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.SetOrRemoveAnnotation)), typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.HasNoEntityType)), + typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.Complex)), typeof(IReadOnlyEntityType).GetMethod(nameof(IReadOnlyEntityType.GetConcreteDerivedTypesInclusive)), typeof(IMutableEntityType).GetMethod(nameof(IMutableEntityType.AddData)), typeof(IReadOnlyNavigationBase).GetMethod("get_DeclaringEntityType"), diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 3c959f66bfb..124ee6a3485 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel; // ReSharper disable UnusedMember.Local // ReSharper disable InconsistentNaming @@ -874,6 +875,50 @@ public virtual void Detects_nonCollection_skip_navigations() modelBuilder); } + [ConditionalFact] + public virtual void Detects_collection_complex_properties() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Ignore(typeof(Order)); + + var model = modelBuilder.Model; + var customerEntity = model.AddEntityType(typeof(Customer)); + customerEntity.AddComplexProperty(nameof(Customer.Orders), collection: true); + + VerifyError( + CoreStrings.ComplexPropertyCollection(nameof(Customer), nameof(Customer.Orders)), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_shadow_complex_properties() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Ignore(typeof(Order)); + + var model = modelBuilder.Model; + var customerEntity = model.AddEntityType(typeof(Customer)); + customerEntity.AddComplexProperty("CustomerDetails", typeof(SponsorDetails), typeof(SponsorDetails)); + + VerifyError( + CoreStrings.ComplexPropertyShadow(nameof(Customer), "CustomerDetails"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_indexer_complex_properties() + { + var modelBuilder = CreateConventionModelBuilder(); + + var model = modelBuilder.Model; + var customerEntity = model.AddEntityType("Customer"); + customerEntity.AddComplexProperty("CustomerDetails", typeof(SponsorDetails), customerEntity.FindIndexerPropertyInfo()!, typeof(SponsorDetails)); + + VerifyError( + CoreStrings.ComplexPropertyIndexer("Customer (Dictionary)", "CustomerDetails"), + modelBuilder); + } + [ConditionalFact] public virtual void Passes_on_valid_owned_entity_types() { @@ -1446,6 +1491,31 @@ public virtual void Detects_collection_navigations_in_seeds(bool sensitiveDataLo sensitiveDataLoggingEnabled); } + [ConditionalFact] + public virtual void Throws_on_two_properties_sharing_a_field() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity().Property(c => c.PartitionId).HasField("_name"); + + VerifyError( + CoreStrings.ConflictingFieldProperty( + nameof(Customer), nameof(Customer.PartitionId), "_name", nameof(Customer), nameof(Customer.Name)), + modelBuilder); + } + + [ConditionalFact] + public virtual void Throws_on_property_using_a_field_mapped_as_another_property() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity().Property(c => c.PartitionId).HasField("OtherName"); + modelBuilder.Entity().Property(c => c.OtherName); + + VerifyError( + CoreStrings.ConflictingFieldProperty( + nameof(Customer), nameof(Customer.PartitionId), nameof(Customer.OtherName), nameof(Customer), nameof(Customer.OtherName)), + modelBuilder); + } + [ConditionalFact] public virtual void Detects_missing_discriminator_property() { diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index fd4ff4ac6ba..63a11271929 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -241,8 +241,15 @@ public int this[int index] protected class Customer { + private string _name; + public string OtherName; + public int Id { get; set; } - public string Name { get; set; } + public string Name + { + get => _name; + set => _name = value; + } public string PartitionId { get; set; } public ICollection Orders { get; set; } } diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 39d15972baf..4390dc6f752 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -3854,7 +3854,7 @@ public void OnComplexTypePropertyAdded_calls_conventions_in_order(bool useBuilde var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var complexBuilder = entityBuilder.ComplexProperty( - Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder; var shadowPropertyName = "ShadowProperty"; @@ -3937,7 +3937,7 @@ public void OnComplexTypePropertyNullabilityChanged_calls_conventions_in_order(b var scope = useScope ? model.DelayConventions() : null; var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder .Property(typeof(string), "Name", ConfigurationSource.Convention); if (useBuilder) @@ -4050,7 +4050,7 @@ public void OnComplexTypePropertyFieldChanged_calls_conventions_in_order(bool us var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var propertyBuilder = entityBuilder - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); @@ -4127,7 +4127,7 @@ public void OnComplexTypePropertyAnnotationChanged_calls_conventions_in_order(bo var builder = new InternalModelBuilder(new Model(conventions)); var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); @@ -4202,7 +4202,7 @@ public void OnComplexTypePropertyRemoved_calls_conventions_in_order(bool useScop var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var shadowPropertyName = "ShadowProperty"; var property = entityBuilder - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder.Metadata.AddProperty( shadowPropertyName, typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); @@ -4255,7 +4255,7 @@ public void OnComplexPropertyAdded_calls_conventions_in_order(bool useBuilder, b if (useBuilder) { var result = entityBuilder.ComplexProperty( - Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention); Assert.Equal(!useScope, result == null); } @@ -4283,7 +4283,7 @@ public void OnComplexPropertyAdded_calls_conventions_in_order(bool useBuilder, b if (useBuilder) { var result = entityBuilder.ComplexProperty( - Order.OtherOrderDetailsProperty, collection: false, ConfigurationSource.Convention); + Order.OtherOrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention); Assert.Equal(!useScope, result == null); } @@ -4357,7 +4357,7 @@ public void OnComplexPropertyNullabilityChanged_calls_conventions_in_order(bool var scope = useScope ? model.DelayConventions() : null; var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention); if (useBuilder) { Assert.NotNull(propertyBuilder.IsRequired(true, ConfigurationSource.Convention)); @@ -4491,7 +4491,7 @@ public void OnComplexPropertyFieldChanged_calls_conventions_in_order(bool useBui var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var propertyBuilder = entityBuilder - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -4593,7 +4593,7 @@ public void OnComplexPropertyAnnotationChanged_calls_conventions_in_order(bool u var builder = new InternalModelBuilder(new Model(conventions)); var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -4693,7 +4693,7 @@ public void OnComplexPropertyRemoved_calls_conventions_in_order(bool useScope) var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var property = entityBuilder - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .Metadata; var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -4765,7 +4765,7 @@ public void OnComplexTypeAnnotationChanged_calls_conventions_in_order(bool useBu var builder = new InternalModelBuilder(new Model(conventions)); var typeBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) - .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder; var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -4868,7 +4868,7 @@ public void OnComplexTypeMemberIgnored_calls_conventions_in_order(bool useBuilde var builder = new InternalModelBuilder(new Model(conventions)); var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); var complexBuilder = entityBuilder.ComplexProperty( - Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + Order.OrderDetailsProperty, complexTypeName: null, collection: false, ConfigurationSource.Convention) .ComplexTypeBuilder; var shadowPropertyName = "ShadowProperty"; diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index 0d751e7c219..34662cd6523 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -2652,7 +2652,7 @@ private bool ConfigureMember( case MemberType.Property: return entityTypeBuilder.Property(Order.ProductsProperty, configurationSource) != null; case MemberType.ComplexProperty: - return entityTypeBuilder.ComplexProperty(Order.ProductsProperty, collection: true, configurationSource) != null; + return entityTypeBuilder.ComplexProperty(Order.ProductsProperty, complexTypeName: null, collection: true, configurationSource) != null; case MemberType.ServiceProperty: return entityTypeBuilder.ServiceProperty(Order.ProductsProperty, configurationSource) != null; case MemberType.Navigation: diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs index ba5eb943bdb..f982254c38d 100644 --- a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -17,10 +17,13 @@ public virtual void Can_set_complex_property_annotation() var modelBuilder = CreateModelBuilder(); var complexPropertyBuilder = modelBuilder + .Ignore() .Entity() .ComplexProperty(e => e.Customer) .HasTypeAnnotation("foo", "bar") - .HasPropertyAnnotation("foo2", "bar2"); + .HasPropertyAnnotation("foo2", "bar2") + .Ignore(c => c.Details) + .Ignore(c => c.Orders); var model = modelBuilder.FinalizeModel(); var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); @@ -43,10 +46,13 @@ public virtual void Can_set_property_annotation() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); modelBuilder + .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Customer) + .Ignore(c => c.Details) + .Ignore(c => c.Orders) .Property(c => c.Name).HasAnnotation("foo", "bar"); var model = modelBuilder.FinalizeModel(); @@ -61,10 +67,13 @@ public virtual void Can_set_property_annotation_when_no_clr_property() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); modelBuilder + .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Customer) + .Ignore(c => c.Details) + .Ignore(c => c.Orders) .Property(Customer.NameProperty.Name).HasAnnotation("foo", "bar"); var model = modelBuilder.FinalizeModel(); @@ -79,11 +88,14 @@ public virtual void Can_set_property_annotation_by_type() { var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); - modelBuilder.Ignore(); - var propertyBuilder = modelBuilder + modelBuilder + .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Customer) - .Property(c => c.Name).HasAnnotation("foo", "bar"); + .Ignore(c => c.Details) + .Ignore(c => c.Orders) + .Property(c => c.Name); var model = modelBuilder.FinalizeModel(); var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); @@ -99,6 +111,7 @@ public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nulla modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -129,6 +142,7 @@ public virtual void Properties_can_be_ignored() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -157,8 +171,9 @@ public virtual void Properties_can_be_ignored_by_type() modelBuilder .Ignore() + .Ignore() .Entity() - .ComplexProperty(e => e.Customer); + .ComplexProperty(e => e.Customer, b => b.Ignore(c => c.Details).Ignore(c => c.Orders)); var model = modelBuilder.FinalizeModel(); var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; @@ -171,8 +186,9 @@ public virtual void Can_ignore_shadow_properties_when_they_have_been_added_expli var modelBuilder = CreateModelBuilder(); var complexPropertyBuilder = modelBuilder + .Ignore() .Entity() - .ComplexProperty(e => e.Customer); + .ComplexProperty(e => e.Customer, b => b.Ignore(c => c.Details).Ignore(c => c.Orders)); complexPropertyBuilder.Property("Shadow"); complexPropertyBuilder.Ignore("Shadow"); @@ -187,12 +203,15 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); modelBuilder + .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Customer, b => { + b.Ignore(c => c.Details); + b.Ignore(c => c.Orders); b.Ignore("Shadow"); b.Property("Shadow"); }); @@ -210,6 +229,7 @@ public virtual void Properties_can_be_made_required() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -240,6 +260,7 @@ public virtual void Properties_can_be_made_optional() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -264,6 +285,7 @@ public virtual void Non_nullable_properties_cannot_be_made_optional() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -296,6 +318,7 @@ public virtual void Properties_specified_by_string_are_shadow_properties_unless_ modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -328,6 +351,7 @@ public virtual void Properties_can_be_made_concurrency_tokens() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -370,6 +394,7 @@ public virtual void Properties_can_have_access_mode_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -398,7 +423,7 @@ public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels( modelBuilder .Entity() - .ComplexProperty(e => e.Customer); + .ComplexProperty(e => e.Customer, b => b.Ignore(c => c.Details).Ignore(c => c.Orders)); modelBuilder .Entity() @@ -434,6 +459,7 @@ public virtual void Properties_can_have_provider_type_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -482,6 +508,7 @@ public virtual void Properties_can_have_provider_type_set_for_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -511,6 +538,7 @@ public virtual void Properties_can_have_non_generic_value_converter_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -547,6 +575,7 @@ public virtual void Properties_can_have_custom_type_value_converter_type_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -607,6 +636,7 @@ public virtual void Properties_can_have_value_converter_set_inline() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -650,6 +680,7 @@ public virtual void Properties_can_have_value_converter_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -696,6 +727,7 @@ public virtual void IEnumerable_properties_with_value_converter_set_are_not_disc modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.DynamicProperty, b => @@ -755,6 +787,7 @@ public virtual void Properties_can_have_value_converter_configured_by_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.WrappedStringEntity); @@ -779,6 +812,7 @@ public virtual void Value_converter_configured_on_non_nullable_type_is_applied() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -811,6 +845,7 @@ public virtual void Value_converter_configured_on_nullable_type_overrides_non_nu modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -847,6 +882,7 @@ public virtual void Value_converter_type_is_checked() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -870,6 +906,7 @@ public virtual void Properties_can_have_field_set() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -926,6 +963,7 @@ public virtual void Properties_can_be_set_to_generate_values_on_Add() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -957,6 +995,7 @@ public virtual void Properties_can_set_row_version() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -986,6 +1025,7 @@ public virtual void Can_set_max_length_for_properties() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1022,6 +1062,7 @@ public virtual void Can_set_max_length_for_property_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1051,6 +1092,7 @@ public virtual void Can_set_sentinel_for_properties() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1087,6 +1129,7 @@ public virtual void Can_set_sentinel_for_property_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1121,6 +1164,7 @@ public virtual void Can_set_unbounded_max_length_for_property_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1150,6 +1194,7 @@ public virtual void Can_set_precision_and_scale_for_properties() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1193,6 +1238,7 @@ public virtual void Can_set_precision_and_scale_for_property_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1229,6 +1275,7 @@ public virtual void Can_set_custom_value_generator_for_properties() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1294,13 +1341,14 @@ public virtual void Throws_for_value_generator_that_cannot_be_constructed() modelBuilder .Entity() .ComplexProperty(e => e.Quarks, - b => - { - b.Property(e => e.Up).HasValueGenerator(); - b.Property(e => e.Down).HasValueGenerator(); - }); + b => + { + b.Property(e => e.Up).HasValueGenerator(); + b.Property(e => e.Down).HasValueGenerator(); + }); - var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var complexType = model.FindEntityType(typeof(ComplexProperties)) + .FindComplexProperty(nameof(ComplexProperties.Quarks))!.ComplexType; Assert.Equal( CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), @@ -1336,6 +1384,7 @@ public virtual void Can_set_unicode_for_properties() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1372,6 +1421,7 @@ public virtual void Can_set_unicode_for_property_type() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Quarks, b => @@ -1425,14 +1475,14 @@ public virtual void Can_call_Property_on_a_field() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.EntityWithFields).Property(e => e.Id); var model = modelBuilder.FinalizeModel(); var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; - var properties = complexType.GetProperties(); - var property = Assert.Single(properties); - Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Equal(1, complexType.GetProperties().Count()); + var property = complexType.FindProperty(nameof(EntityWithFields.Id)); Assert.Null(property.PropertyInfo); Assert.NotNull(property.FieldInfo); } @@ -1444,6 +1494,7 @@ public virtual void Can_ignore_a_field() modelBuilder .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.EntityWithFields, b => { b.Property(e => e.Id); @@ -1452,8 +1503,7 @@ public virtual void Can_ignore_a_field() var model = modelBuilder.FinalizeModel(); var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); - var property = Assert.Single(complexProperty.ComplexType.GetProperties()); - Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Equal(1, complexProperty.ComplexType.GetProperties().Count()); } [ConditionalFact] @@ -1461,18 +1511,24 @@ public virtual void Complex_properties_discovered_by_convention() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); modelBuilder + .Ignore() + .Ignore() + .Ignore() .Entity() .ComplexProperty(e => e.Customer); modelBuilder .Entity() + .Ignore(e => e.Tuple) .ComplexProperty(e => e.Label); var model = modelBuilder.FinalizeModel(); - var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var customerType = model.FindEntityType(typeof(ComplexProperties)) + .FindComplexProperty(nameof(ComplexProperties.Customer)).ComplexType; + var indexedType = model.FindEntityType(typeof(ComplexProperties)) + .FindComplexProperty(nameof(ComplexProperties.IndexedClass)).ComplexType; var valueType = model.FindEntityType(typeof(ValueComplexProperties)); var labelProperty = valueType.FindComplexProperty(nameof(ValueComplexProperties.Label)); @@ -1495,5 +1551,80 @@ public virtual void Complex_properties_discovered_by_convention() Assert.True(oldLabelCustomerProperty.IsNullable); Assert.Equal(typeof(Customer), oldLabelCustomerProperty.ClrType); } + + [ConditionalFact] + public virtual void Complex_properties_can_be_configured_by_type() + { + var modelBuilder = CreateModelBuilder(m => m.ComplexProperties()); + + modelBuilder + .Ignore() + .Ignore() + .Ignore() + .Ignore() + .Entity(); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(typeof(Customer), complexType.ClrType); + } + + [ConditionalFact] + public virtual void Can_map_tuple() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .Ignore(e => e.Label) + .Ignore(e => e.OldLabel) + .ComplexProperty(e => e.Tuple); + + var model = modelBuilder.FinalizeModel(); + + var valueType = model.FindEntityType(typeof(ValueComplexProperties)); + var tupleProperty = valueType.FindComplexProperty(nameof(ValueComplexProperties.Tuple)); + Assert.False(tupleProperty.IsNullable); + Assert.Equal(typeof((string, int)), tupleProperty.ClrType); + var tupleType = tupleProperty.ComplexType; + Assert.Equal(typeof((string, int)), tupleType.ClrType); + Assert.Equal("ValueComplexProperties.Tuple#ValueTuple", tupleType.DisplayName()); + + Assert.Equal(2, tupleType.GetProperties().Count()); + } + + [ConditionalFact] + protected virtual void Mapping_throws_for_non_ignored_navigations_on_complex_types() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer); + + Assert.Equal( + CoreStrings.NavigationNotAddedComplexType( + "ComplexProperties.Customer#Customer", nameof(Customer.Details), typeof(CustomerDetails).ShortDisplayName()), + Assert.Throws(modelBuilder.FinalizeModel).Message); + } + + [ConditionalFact] + protected virtual void Mapping_throws_for_empty_complex_types() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Ignore(c => c.Name) + .Ignore(c => c.Id) + .Ignore(c => c.AlternateKey); + + Assert.Equal( + CoreStrings.EmptyComplexType( + "ComplexProperties.Customer#Customer"), + Assert.Throws(modelBuilder.FinalizeModel).Message); + } } } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 252de0dfadd..43db9739093 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -233,12 +233,26 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyExpression)); - public override TestComplexPropertyBuilder ComplexProperty(string propertyName) - => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + => new GenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(propertyExpression, complexTypeName)); + + public override TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + + return this; + } public override TestEntityTypeBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction) @@ -249,9 +263,12 @@ public override TestEntityTypeBuilder ComplexProperty( } public override TestEntityTypeBuilder ComplexProperty( - string propertyName, Action> buildAction) + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) { - buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + buildAction(new GenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(propertyExpression, complexTypeName))); return this; } @@ -488,12 +505,25 @@ public override TestComplexTypePropertyBuilder Property(st public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) => Wrap(PropertyBuilder.ComplexProperty(propertyExpression)); - public override TestComplexPropertyBuilder ComplexProperty(string propertyName) - => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + => Wrap(PropertyBuilder.ComplexProperty(propertyExpression, complexTypeName)); + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction) @@ -504,9 +534,11 @@ public override TestComplexPropertyBuilder ComplexProperty( } public override TestComplexPropertyBuilder ComplexProperty( - string propertyName, Action> buildAction) + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) { - buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyExpression, complexTypeName))); return this; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index ad3de464d75..95491e3608b 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -262,6 +262,9 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) { @@ -270,8 +273,14 @@ public override TestComplexPropertyBuilder ComplexProperty EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); } - public override TestComplexPropertyBuilder ComplexProperty(string propertyName) - => new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + } public override TestEntityTypeBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction) @@ -283,6 +292,18 @@ public override TestEntityTypeBuilder ComplexProperty( return this; } + public override TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) + { + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); + + return this; + } + public override TestEntityTypeBuilder ComplexProperty( string propertyName, Action> buildAction) { @@ -560,6 +581,9 @@ public override TestComplexTypePropertyBuilder Property(st public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression) { @@ -567,8 +591,23 @@ public override TestComplexPropertyBuilder ComplexProperty return Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); } - public override TestComplexPropertyBuilder ComplexProperty(string propertyName) - => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.ComplexProperty( + memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName)); + } + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, + Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } public override TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction) @@ -580,9 +619,13 @@ public override TestComplexPropertyBuilder ComplexProperty( } public override TestComplexPropertyBuilder ComplexProperty( - string propertyName, Action> buildAction) + Expression> propertyExpression, + string complexTypeName, + Action> buildAction) { - buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(Wrap(PropertyBuilder.ComplexProperty( + memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), complexTypeName))); return this; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 2f3c0030a6b..7a6a14f6e3b 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -101,7 +101,7 @@ protected TestModelBuilder HobNobBuilder() protected abstract TestModelBuilder CreateTestModelBuilder( TestHelpers testHelpers, - Action? configure); + Action? configure = null); } public abstract class TestModelBuilder : IInfrastructure @@ -203,14 +203,23 @@ public abstract TestPropertyBuilder Property( public abstract TestPropertyBuilder Property(string propertyName); public abstract TestPropertyBuilder IndexerProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression); - public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName); public abstract TestEntityTypeBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction); + public abstract TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName, + Action> buildAction); + public abstract TestEntityTypeBuilder ComplexProperty( string propertyName, Action> buildAction); @@ -359,14 +368,23 @@ public abstract TestComplexTypePropertyBuilder Property( public abstract TestComplexTypePropertyBuilder Property(string propertyName); public abstract TestComplexTypePropertyBuilder IndexerProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression); - public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName); public abstract TestComplexPropertyBuilder ComplexProperty( Expression> propertyExpression, Action> buildAction); + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, + string complexTypeName, + Action> buildAction); + public abstract TestComplexPropertyBuilder ComplexProperty( string propertyName, Action> buildAction); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index c10579089e5..c0751dab34e 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -336,7 +336,7 @@ public virtual void Can_set_property_annotation_by_type() modelBuilder.Ignore(); var propertyBuilder = modelBuilder .Entity() - .Property(c => c.Name).HasAnnotation("foo", "bar"); + .Property(c => c.Name); var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name)); @@ -1735,7 +1735,7 @@ protected virtual void Mapping_throws_for_non_ignored_three_dimensional_array() Assert.Equal( CoreStrings.PropertyNotAdded( typeof(ThreeDee).ShortDisplayName(), "Three", typeof(int[,,]).ShortDisplayName()), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(modelBuilder.FinalizeModel).Message); } [ConditionalFact] @@ -2051,7 +2051,7 @@ public virtual void Can_set_composite_key_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_alternate_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); modelBuilder.Entity().HasAlternateKey(e => e.CompanyId); @@ -2070,7 +2070,7 @@ public virtual void Can_set_alternate_key_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); modelBuilder.Entity().HasAlternateKey(e => new { e.TenantId, e.CompanyId }); @@ -2091,7 +2091,7 @@ public virtual void Can_set_composite_alternate_key_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_call_Property_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); modelBuilder.Entity().Property(e => e.Id); @@ -2143,7 +2143,7 @@ public virtual void Can_set_composite_index_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_ignore_a_field_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); modelBuilder.Entity() .Ignore(e => e.CompanyId) @@ -2158,7 +2158,7 @@ public virtual void Can_ignore_a_field_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_ignore_a_field_on_a_keyless_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateTestModelBuilder(InMemoryTestHelpers.Instance); modelBuilder.Entity() .HasNoKey() diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 28e4293f1f5..5bb1f728954 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -875,6 +875,7 @@ protected class ValueComplexProperties public int Id { get; set; } public ProductLabel Label { get; set; } public ProductLabel? OldLabel { get; set; } + public (string, int) Tuple { get; set; } } protected struct ProductLabel @@ -919,6 +920,7 @@ int IReplaceable.Property } } + [ComplexType] protected class IndexedClass { private int _required;