diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index e0161316d14..a940d9be61e 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -139,6 +139,16 @@ protected override void ValidatePropertyMapping( if (complexProperty.ComplexType.IsMappedToJson()) { + if (!complexProperty.DeclaringType.IsMappedToJson() + && complexProperty.DeclaringType is IComplexType) + { + // Issue #36558 + throw new InvalidOperationException( + RelationalStrings.NestedComplexPropertyJsonWithTableSharing( + $"{complexProperty.DeclaringType.DisplayName()}.{complexProperty.Name}", + complexProperty.DeclaringType.DisplayName())); + } + ValidateJsonProperties(complexProperty.ComplexType); } } @@ -2702,7 +2712,7 @@ protected virtual void ValidateJsonEntities( var nonJsonType = mappedTypes.Where(x => !x.IsMappedToJson()).First(); // must be an owned collection (mapped to a separate table) that owns a JSON type - // issue #28441 + // Issue #28441 throw new InvalidOperationException( RelationalStrings.JsonEntityOwnedByNonJsonOwnedType( nonJsonType.DisplayName(), table.DisplayName())); @@ -2711,7 +2721,7 @@ protected virtual void ValidateJsonEntities( var distinctRootTypes = nonOwnedTypes.Select(x => x.GetRootType()).Distinct().ToList(); if (distinctRootTypes.Count > 1) { - // issue #28442 + // Issue #28442 throw new InvalidOperationException( RelationalStrings.JsonEntityWithTableSplittingIsNotSupported); } @@ -2966,26 +2976,36 @@ private static Dictionary ValidateJsonProperties(IConventionType foreach (var property in typeBase.GetProperties()) { var jsonPropertyName = property.GetJsonPropertyName(); - if (!string.IsNullOrEmpty(jsonPropertyName)) + if (string.IsNullOrEmpty(jsonPropertyName)) { - var columnNameAnnotation = property.FindAnnotation(RelationalAnnotationNames.ColumnName); - if (columnNameAnnotation != null && !string.IsNullOrEmpty((string?)columnNameAnnotation.Value)) - { - throw new InvalidOperationException( - RelationalStrings.PropertyBothColumnNameAndJsonPropertyName( - $"{typeBase.DisplayName()}.{property.Name}", - (string)columnNameAnnotation.Value, - jsonPropertyName)); - } + continue; + } - if (property.TryGetDefaultValue(out var _)) - { - throw new InvalidOperationException( - RelationalStrings.JsonEntityWithDefaultValueSetOnItsProperty( - typeBase.DisplayName(), property.Name)); - } + var columnNameAnnotation = property.FindAnnotation(RelationalAnnotationNames.ColumnName); + if (columnNameAnnotation != null && !string.IsNullOrEmpty((string?)columnNameAnnotation.Value)) + { + throw new InvalidOperationException( + RelationalStrings.PropertyBothColumnNameAndJsonPropertyName( + $"{typeBase.DisplayName()}.{property.Name}", + (string)columnNameAnnotation.Value, + jsonPropertyName)); + } - CheckUniqueness(jsonPropertyName, property.Name, typeBase, jsonPropertyNames); + if (property.TryGetDefaultValue(out var _)) + { + // Issue #35934 + throw new InvalidOperationException( + RelationalStrings.JsonEntityWithDefaultValueSetOnItsProperty( + typeBase.DisplayName(), property.Name)); + } + + CheckUniqueness(jsonPropertyName, property.Name, typeBase, jsonPropertyNames); + + if (property.IsConcurrencyToken) + { + throw new InvalidOperationException( + RelationalStrings.ConcurrencyTokenOnJsonMappedProperty( + property.Name, typeBase.DisplayName())); } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 45aea183d1a..a0f664d10d4 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -608,7 +608,12 @@ private static void CreateContainerColumn( Check.DebugAssert(tableBase.FindColumn(containerColumnName) == null, $"Table '{tableBase.Name}' already has a '{containerColumnName}' column."); - var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonTypePlaceholder), storeTypeName: containerColumnType)!; + var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonTypePlaceholder), storeTypeName: containerColumnType); + if (jsonColumnTypeMapping == null) + { + throw new InvalidOperationException(RelationalStrings.UnsupportedJsonColumnType(containerColumnType ?? "null", containerColumnName, tableBase.Name)); + } + var jsonColumn = createColumn(containerColumnName, containerColumnType, tableBase, jsonColumnTypeMapping); tableBase.Columns.Add(containerColumnName, jsonColumn); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 6ad7fec2d3c..6cbd14cfa86 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -129,6 +129,14 @@ public static string ComputedColumnSqlUnspecified(object? table, object? column) GetString("ComputedColumnSqlUnspecified", nameof(table), nameof(column)), table, column); + /// + /// The property '{property}' on '{type}' is configured as a concurrency token, but the type is mapped to JSON. Concurrency tokens are not supported on JSON-mapped types. + /// + public static string ConcurrencyTokenOnJsonMappedProperty(object? property, object? type) + => string.Format( + GetString("ConcurrencyTokenOnJsonMappedProperty", nameof(property), nameof(type)), + property, type); + /// /// An ambient transaction has been detected. The ambient transaction needs to be completed before starting a new transaction on this connection. /// @@ -1465,6 +1473,14 @@ public static string NestedCollectionsNotSupported(object? propertyType, object? GetString("NestedCollectionsNotSupported", nameof(propertyType), nameof(type), nameof(property)), propertyType, type, property); + /// + /// Complex property '{complexProperty}' is mapped to JSON but its containing type '{containingType}' is not. Map the root complex type to JSON. See https://github.com/dotnet/efcore/issues/36558 + /// + public static string NestedComplexPropertyJsonWithTableSharing(object? complexProperty, object? containingType) + => string.Format( + GetString("NestedComplexPropertyJsonWithTableSharing", nameof(complexProperty), nameof(containingType)), + complexProperty, containingType); + /// /// The connection does not have any active transactions. /// @@ -2185,6 +2201,14 @@ public static string UnsupportedDataOperationStoreType(object? type, object? col GetString("UnsupportedDataOperationStoreType", nameof(type), nameof(column)), type, column); + /// + /// The store type '{storeType}' specified for JSON column '{columnName}' in table '{tableName}' is not supported by the current provider. JSON columns require a provider-specific JSON store type. + /// + public static string UnsupportedJsonColumnType(object? storeType, object? columnName, object? tableName) + => string.Format( + GetString("UnsupportedJsonColumnType", nameof(storeType), nameof(columnName), nameof(tableName)), + storeType, columnName, tableName); + /// /// Unsupported operator '{nodeType}' specified for expression of type '{expressionType}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 5be7983829e..62b52cfc81d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -160,6 +160,9 @@ The computed column SQL has not been specified for the column '{table}.{column}'. Specify the SQL before using Entity Framework to create the database schema. + + The property '{property}' on '{type}' is configured as a concurrency token, but the type is mapped to JSON. Concurrency tokens are not supported on JSON-mapped types. + An ambient transaction has been detected. The ambient transaction needs to be completed before starting a new transaction on this connection. @@ -1003,6 +1006,9 @@ The property '{propertyType} {type}.{property}' is a primitive collection of a primitive collection. Nested primitive collections are not yet supported with relational database providers. + + Complex property '{complexProperty}' is mapped to JSON but its containing type '{containingType}' is not. Map the root complex type to JSON. See https://github.com/dotnet/efcore/issues/36558 + The connection does not have any active transactions. @@ -1285,6 +1291,9 @@ The store type '{type}' used for the column '{column}' in a migration data operation is not supported by the current provider. + + The store type '{storeType}' specified for JSON column '{columnName}' in table '{tableName}' is not supported by the current provider. JSON columns require a provider-specific JSON store type. + Unsupported operator '{nodeType}' specified for expression of type '{expressionType}'. diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs index 032f7d961b6..b6ab8cb2928 100644 --- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs +++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs @@ -750,7 +750,7 @@ private void LogChangeDetected(IInternalEntry entry, IProperty property, object? } else { - _logger.ComplexTypePropertyChangeDetectedSensitive((InternalComplexEntry)entry, property, original, current); + _logger.ComplexElementPropertyChangeDetectedSensitive((InternalComplexEntry)entry, property, original, current); } } else @@ -761,7 +761,7 @@ private void LogChangeDetected(IInternalEntry entry, IProperty property, object? } else { - _logger.ComplexTypePropertyChangeDetected((InternalComplexEntry)entry, property, original, current); + _logger.ComplexElementPropertyChangeDetected((InternalComplexEntry)entry, property, original, current); } } } diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 5467ba54048..3ccfac94727 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -139,7 +139,7 @@ private enum Id StateChanged, ValueGenerated, SkipCollectionChangeDetected, - ComplexTypePropertyChangeDetected + ComplexElementPropertyChangeDetected } private static readonly string _updatePrefix = DbLoggerCategory.Update.Name + "."; @@ -986,7 +986,7 @@ private static EventId MakeChangeTrackingId(Id id) /// . /// /// - public static readonly EventId ComplexTypePropertyChangeDetected = MakeChangeTrackingId(Id.ComplexTypePropertyChangeDetected); + public static readonly EventId ComplexElementPropertyChangeDetected = MakeChangeTrackingId(Id.ComplexElementPropertyChangeDetected); /// /// DetectChanges has detected a change in a foreign key property value. diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index d47c7cfc9ff..9fb70ad7d5d 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -2290,21 +2290,21 @@ private static string PropertyChangeDetectedSensitive(EventDefinitionBase defini } /// - /// Logs for the event. + /// Logs for the event. /// /// The diagnostics logger to use. /// The internal complex entry. /// The property. /// The old value. /// The new value. - public static void ComplexTypePropertyChangeDetected( + public static void ComplexElementPropertyChangeDetected( this IDiagnosticsLogger diagnostics, InternalComplexEntry internalComplexEntry, IProperty property, object? oldValue, object? newValue) { - var definition = CoreResources.LogComplexTypePropertyChangeDetected(diagnostics); + var definition = CoreResources.LogComplexElementPropertyChangeDetected(diagnostics); if (diagnostics.ShouldLog(definition)) { @@ -2315,7 +2315,7 @@ public static void ComplexTypePropertyChangeDetected( { var eventData = new ComplexTypePropertyChangedEventData( definition, - ComplexTypePropertyChangeDetected, + ComplexElementPropertyChangeDetected, new ComplexElementEntry(internalComplexEntry), property, oldValue, @@ -2325,7 +2325,7 @@ public static void ComplexTypePropertyChangeDetected( } } - private static string ComplexTypePropertyChangeDetected(EventDefinitionBase definition, EventData payload) + private static string ComplexElementPropertyChangeDetected(EventDefinitionBase definition, EventData payload) { var d = (EventDefinition)definition; var p = (ComplexTypePropertyChangedEventData)payload; @@ -2335,21 +2335,21 @@ private static string ComplexTypePropertyChangeDetected(EventDefinitionBase defi } /// - /// Logs for the event. + /// Logs for the event. /// /// The diagnostics logger to use. /// The internal complex entry. /// The property. /// The old value. /// The new value. - public static void ComplexTypePropertyChangeDetectedSensitive( + public static void ComplexElementPropertyChangeDetectedSensitive( this IDiagnosticsLogger diagnostics, InternalComplexEntry internalComplexEntry, IProperty property, object? oldValue, object? newValue) { - var definition = CoreResources.LogComplexTypePropertyChangeDetectedSensitive(diagnostics); + var definition = CoreResources.LogComplexElementPropertyChangeDetectedSensitive(diagnostics); if (diagnostics.ShouldLog(definition)) { @@ -2366,7 +2366,7 @@ public static void ComplexTypePropertyChangeDetectedSensitive( { var eventData = new ComplexTypePropertyChangedEventData( definition, - ComplexTypePropertyChangeDetectedSensitive, + ComplexElementPropertyChangeDetectedSensitive, new ComplexElementEntry(internalComplexEntry), property, oldValue, @@ -2376,7 +2376,7 @@ public static void ComplexTypePropertyChangeDetectedSensitive( } } - private static string ComplexTypePropertyChangeDetectedSensitive(EventDefinitionBase definition, EventData payload) + private static string ComplexElementPropertyChangeDetectedSensitive(EventDefinitionBase definition, EventData payload) { var d = (EventDefinition)definition; var p = (ComplexTypePropertyChangedEventData)payload; diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index 25095796486..04145a1fed6 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -527,7 +527,7 @@ public abstract class LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public EventDefinitionBase? LogComplexTypePropertyChangeDetected; + public EventDefinitionBase? LogComplexElementPropertyChangeDetected; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -536,7 +536,7 @@ public abstract class LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public EventDefinitionBase? LogComplexTypePropertyChangeDetectedSensitive; + public EventDefinitionBase? LogComplexElementPropertyChangeDetectedSensitive; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 2c6d4506c69..346193b7137 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -354,6 +354,14 @@ protected virtual void ValidatePropertyMapping(IConventionComplexProperty comple CoreStrings.ComplexValueTypeShadowProperty(complexProperty.ComplexType.DisplayName(), shadowProperty.Name)); } } + + // Issue #35613: Shadow properties on all complex types are not supported + var shadowPropertyOnComplexType = complexProperty.ComplexType.GetDeclaredProperties().FirstOrDefault(p => p.IsShadowProperty()); + if (shadowPropertyOnComplexType != null) + { + throw new InvalidOperationException( + CoreStrings.ComplexTypeShadowProperty(complexProperty.ComplexType.DisplayName(), shadowPropertyOnComplexType.Name)); + } } /// diff --git a/src/EFCore/Metadata/IConventionNavigationBase.cs b/src/EFCore/Metadata/IConventionNavigationBase.cs index e7b92c910b7..ecd7b55bc8f 100644 --- a/src/EFCore/Metadata/IConventionNavigationBase.cs +++ b/src/EFCore/Metadata/IConventionNavigationBase.cs @@ -19,6 +19,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public interface IConventionNavigationBase : IReadOnlyNavigationBase, IConventionPropertyBase { + /// + /// Gets the entity type that this navigation property will hold an instance(s) of. + /// + new IConventionEntityType TargetEntityType + { + [DebuggerStepThrough] + get => (IConventionEntityType)((IReadOnlyNavigationBase)this).TargetEntityType; + } + /// /// Sets a value indicating whether this navigation should be eager loaded by default. /// diff --git a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs index 4c37fd883f5..24207a018e4 100644 --- a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs @@ -141,15 +141,14 @@ private static Expression CreateConvertedValueExpression(Expression valueParamet return convertedParameter; } - private static Expression CreateSimplePropertyAssignment(MemberInfo memberInfo, IPropertyBase? propertyBase, Expression instanceParameter, Expression convertedParameter) - { - return propertyBase?.IsIndexerProperty() == true + private static Expression CreateSimplePropertyAssignment( + MemberInfo memberInfo, IPropertyBase? propertyBase, Expression instanceParameter, Expression convertedParameter) + => propertyBase?.IsIndexerProperty() == true ? Assign( MakeIndex( instanceParameter, (PropertyInfo)memberInfo, [Constant(propertyBase.Name)]), convertedParameter) : MakeMemberAccess(instanceParameter, memberInfo).Assign(convertedParameter); - } private void CreateExpressionUsingContainingEntity( MemberInfo memberInfo, @@ -284,7 +283,8 @@ static Expression CreateMemberAssignment( } var propertyMemberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: true); - statements.Add(MakeMemberAccess(previousLevel, propertyMemberInfo).Assign(convertedParameter)); + statements.Add(CreateSimplePropertyAssignment( + propertyMemberInfo, propertyBase, previousLevel, convertedParameter)); for (var i = 0; i <= chainCount - 1; i++) { @@ -314,7 +314,8 @@ static Expression CreateMemberAssignment( indicesParameter, complexMemberInfo, fromDeclaringType: true, - fromEntity: false); + fromEntity: false, + addNullCheck: false); statements.Add(memberExpression.Assign(variables[chainCount - 1 - i])); } diff --git a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs index cb1207c81a5..aa4ddd336a4 100644 --- a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs +++ b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs @@ -120,7 +120,9 @@ private ComplexType ComplexType } } - return MergeConfiguration(complexPropertyBuilder); + return complexPropertyBuilder.Metadata.IsCollection == ComplexProperty.IsCollection + ? MergeConfiguration(complexPropertyBuilder) + : complexPropertyBuilder; } private InternalComplexPropertyBuilder MergeConfiguration(InternalComplexPropertyBuilder complexPropertyBuilder) diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs index ace8ad64b6a..e7860529d76 100644 --- a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -1051,9 +1051,6 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo Debug.Assert(configurationSource.HasValue); memberInfo ??= existingComplexProperty.PropertyInfo ?? (MemberInfo?)existingComplexProperty.FieldInfo; - propertyType ??= existingComplexProperty.ClrType; - collection ??= existingComplexProperty.IsCollection; - complexType ??= existingComplexType.ClrType; propertiesToDetach = [existingComplexProperty]; } @@ -1067,33 +1064,6 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo return null; } - memberInfo ??= Metadata.IsPropertyBag - ? null - : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); - - if (propertyType == null) - { - if (memberInfo == null) - { - throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); - } - - propertyType = memberInfo.GetMemberType(); - } - - if (collection == false) - { - complexType = propertyType.UnwrapNullableType(); - } - - if (collection == null - || complexType == null) - { - var elementType = propertyType.TryGetSequenceType(); - collection ??= elementType != null; - complexType ??= (collection.Value ? elementType : propertyType)?.UnwrapNullableType(); - } - foreach (var derivedType in Metadata.GetDerivedTypes()) { var derivedProperty = derivedType.FindDeclaredComplexProperty(propertyName); @@ -1104,6 +1074,33 @@ protected virtual void RemovePropertyIfUnused(Property property, ConfigurationSo propertiesToDetach.Add(derivedProperty); } } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + } + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + if (collection == false) + { + complexType = propertyType.UnwrapNullableType(); + } + + if (collection == null + || complexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + complexType ??= (collection.Value ? elementType : propertyType)?.UnwrapNullableType(); } var model = Metadata.Model; @@ -1517,7 +1514,8 @@ private bool CanAddDiscriminatorProperty( return memberInfo == null || propertyType == memberInfo.GetMemberType() - || typeConfigurationSource == null; + || typeConfigurationSource == null + || typeConfigurationSource == ConfigurationSource.Explicit; } /// diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs index 26eecb14713..954b3d77c45 100644 --- a/src/EFCore/Metadata/Internal/MemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -219,20 +220,25 @@ public virtual bool IsCandidateComplexProperty( var targetType = memberInfo.GetMemberType(); if (targetType.TryGetElementType(typeof(IList<>)) is Type sequenceType - && IsCandidateComplexType(sequenceType, model, out explicitlyConfigured)) + && IsCandidateComplexType(sequenceType.UnwrapNullableType(), model, out explicitlyConfigured)) { elementType = sequenceType; return true; } - return IsCandidateComplexType(targetType, model, out explicitlyConfigured); + return IsCandidateComplexType(targetType.UnwrapNullableType(), model, out explicitlyConfigured); } private static bool IsCandidateComplexType(Type targetType, IConventionModel model, out bool explicitlyConfigured) { if (!targetType.IsValidComplexType() || (targetType.IsGenericType - && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>))) + && targetType.GetGenericTypeDefinition() is Type genericTypeDefinition + && (genericTypeDefinition == typeof(Dictionary<,>) + || genericTypeDefinition == typeof(List<>) + || genericTypeDefinition == typeof(HashSet<>) + || genericTypeDefinition == typeof(Collection<>) + || genericTypeDefinition == typeof(ObservableCollection<>)))) { explicitlyConfigured = false; return false; diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs index a00eb798b3e..5f5fd275929 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs @@ -273,27 +273,11 @@ public static Expression CreateMemberAccess( Expression indicesExpression, MemberInfo memberInfo, bool fromDeclaringType, - bool fromEntity) + bool fromEntity, + bool addNullCheck = true) { Check.DebugAssert(!fromEntity || !fromDeclaringType, "fromEntity and fromDeclaringType can't both be true"); - if (property?.IsIndexerProperty() == true) - { - Expression expression = Expression.MakeIndex( - instanceExpression, (PropertyInfo)memberInfo, [Expression.Constant(property.Name)]); - - if (property.DeclaringType.IsPropertyBag) - { - expression = Expression.Condition( - Expression.Call( - instanceExpression, ContainsKeyMethod, [Expression.Constant(property.Name)]), - expression, - expression.Type.GetDefaultValueConstant()); - } - - return expression; - } - if (!fromDeclaringType && property?.DeclaringType is IRuntimeComplexType complexType) { @@ -309,23 +293,47 @@ public static Expression CreateMemberAccess( indicesExpression, complexProperty.GetMemberInfo(forMaterialization: false, forSet: false), fromDeclaringType, - fromEntity); + fromEntity, + addNullCheck); break; default: - return instanceExpression.MakeMemberAccess(memberInfo); + addNullCheck = false; + break; } + } + else + { + addNullCheck = false; + } + + Expression? memberAccess; + if (property?.IsIndexerProperty() == true) + { + memberAccess = Expression.MakeIndex( + instanceExpression, (PropertyInfo)memberInfo, [Expression.Constant(property.Name)]); - if (!instanceExpression.Type.IsValueType - || instanceExpression.Type.IsNullableValueType()) + if (property.DeclaringType.IsPropertyBag) { - return Expression.Condition( - Expression.Equal(instanceExpression, Expression.Constant(null)), - Expression.Default(memberInfo.GetMemberType()), - instanceExpression.MakeMemberAccess(memberInfo)); + memberAccess = Expression.Condition( + Expression.Call( + instanceExpression, ContainsKeyMethod, [Expression.Constant(property.Name)]), + memberAccess, + memberAccess.Type.GetDefaultValueConstant()); } } + else + { + memberAccess = instanceExpression.MakeMemberAccess(memberInfo); + } - return instanceExpression.MakeMemberAccess(memberInfo); + return !addNullCheck + || instanceExpression.Type.IsValueType + && !instanceExpression.Type.IsNullableValueType() + ? memberAccess + : Expression.Condition( + Expression.Equal(instanceExpression, Expression.Constant(null)), + Expression.Default(memberInfo.GetMemberType()), + memberAccess); } private static readonly MethodInfo ComplexCollectionNotInitializedMethod diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index bcdfb378ad4..0aa03f4faf8 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -351,7 +351,7 @@ public static string CannotMaterializeAbstractType(object? entityType) entityType); /// - /// Navigation '{1_entityType}.{0_navigationName}' was not found. Please add the navigation to the entity type before configuring it. + /// Navigation '{1_entityType}.{0_navigationName}' was not found. Please add the navigation to the entity type using HasOne, HasMany, or OwnsOne/OwnsMany methods before configuring it. /// public static string CanOnlyConfigureExistingNavigations(object? navigationName, object? entityType) => string.Format( @@ -710,6 +710,14 @@ public static string ComplexTypeNotificationChangeTracking(object? complexType, GetString("ComplexTypeNotificationChangeTracking", nameof(complexType), nameof(changeTrackingStrategy)), complexType, changeTrackingStrategy); + /// + /// The shadow property '{complexType}.{property}' cannot be configured on the complex type '{complexType}'. Shadow properties are not supported on complex types. See https://github.com/dotnet/efcore/issues/35613 for more information. + /// + public static string ComplexTypeShadowProperty(object? complexType, object? property) + => string.Format( + GetString("ComplexTypeShadowProperty", nameof(complexType), nameof(property)), + complexType, property); + /// /// '{service}' doesn't currently support complex types. /// @@ -3728,23 +3736,23 @@ public static EventDefinition LogCollectionWithoutComparer(IDiag /// /// The unchanged property '{typePath}.{property}' was detected as changed and will be marked as modified. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see property values. /// - public static EventDefinition LogComplexTypePropertyChangeDetected(IDiagnosticsLogger logger) + public static EventDefinition LogComplexElementPropertyChangeDetected(IDiagnosticsLogger logger) { - var definition = ((LoggingDefinitions)logger.Definitions).LogComplexTypePropertyChangeDetected; + var definition = ((LoggingDefinitions)logger.Definitions).LogComplexElementPropertyChangeDetected; if (definition == null) { definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((LoggingDefinitions)logger.Definitions).LogComplexTypePropertyChangeDetected, + ref ((LoggingDefinitions)logger.Definitions).LogComplexElementPropertyChangeDetected, logger, static logger => new EventDefinition( logger.Options, - CoreEventId.ComplexTypePropertyChangeDetected, + CoreEventId.ComplexElementPropertyChangeDetected, LogLevel.Debug, "CoreEventId.ComplexTypePropertyChangeDetected", level => LoggerMessage.Define( level, - CoreEventId.ComplexTypePropertyChangeDetected, - _resourceManager.GetString("LogComplexTypePropertyChangeDetected")!))); + CoreEventId.ComplexElementPropertyChangeDetected, + _resourceManager.GetString("LogComplexElementPropertyChangeDetected")!))); } return (EventDefinition)definition; @@ -3753,23 +3761,23 @@ public static EventDefinition LogComplexTypePropertyChangeDetect /// /// The unchanged property '{typePath}.{property}' was detected as changed from '{oldValue}' to '{newValue}' and will be marked as modified for entity with key '{keyValues}'. /// - public static EventDefinition LogComplexTypePropertyChangeDetectedSensitive(IDiagnosticsLogger logger) + public static EventDefinition LogComplexElementPropertyChangeDetectedSensitive(IDiagnosticsLogger logger) { - var definition = ((LoggingDefinitions)logger.Definitions).LogComplexTypePropertyChangeDetectedSensitive; + var definition = ((LoggingDefinitions)logger.Definitions).LogComplexElementPropertyChangeDetectedSensitive; if (definition == null) { definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((LoggingDefinitions)logger.Definitions).LogComplexTypePropertyChangeDetectedSensitive, + ref ((LoggingDefinitions)logger.Definitions).LogComplexElementPropertyChangeDetectedSensitive, logger, static logger => new EventDefinition( logger.Options, - CoreEventId.ComplexTypePropertyChangeDetected, + CoreEventId.ComplexElementPropertyChangeDetected, LogLevel.Debug, "CoreEventId.ComplexTypePropertyChangeDetected", level => LoggerMessage.Define( level, - CoreEventId.ComplexTypePropertyChangeDetected, - _resourceManager.GetString("LogComplexTypePropertyChangeDetectedSensitive")!))); + CoreEventId.ComplexElementPropertyChangeDetected, + _resourceManager.GetString("LogComplexElementPropertyChangeDetectedSensitive")!))); } return (EventDefinition)definition; diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 51132abdffb..d3fed65c790 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -375,6 +375,9 @@ Complex type '{complexType}' uses change tracking strategy '{changeTrackingStrategy}', but complex types with notification change tracking are not supported. See https://github.com/dotnet/efcore/issues/36175 for more information. + + The shadow property '{complexType}.{property}' cannot be configured on the complex type '{complexType}'. Shadow properties are not supported on complex types. See https://github.com/dotnet/efcore/issues/35613 for more information. + '{service}' doesn't currently support complex types. @@ -896,11 +899,11 @@ The property '{entityType}.{property}' is a collection or enumeration type with a value converter but with no value comparer. Set a value comparer to ensure the collection/enumeration elements are compared correctly. Warning CoreEventId.CollectionWithoutComparer string string - + The unchanged property '{typePath}.{property}' was detected as changed and will be marked as modified. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see property values. Debug CoreEventId.ComplexTypePropertyChangeDetected string string - + The unchanged property '{typePath}.{property}' was detected as changed from '{oldValue}' to '{newValue}' and will be marked as modified for entity with key '{keyValues}'. Debug CoreEventId.ComplexTypePropertyChangeDetected string string object? object? string diff --git a/src/EFCore/Query/IStructuralTypeMaterializerSource.cs b/src/EFCore/Query/IStructuralTypeMaterializerSource.cs index 770d03aa307..c7443edef1a 100644 --- a/src/EFCore/Query/IStructuralTypeMaterializerSource.cs +++ b/src/EFCore/Query/IStructuralTypeMaterializerSource.cs @@ -94,8 +94,8 @@ public interface IStructuralTypeMaterializerSource } /// -/// This interface has been obsoleted, use instead. +/// This interface has been obsoleted, use instead. /// -[Obsolete("This interface has been obsoleted, use IEntityMaterializerSource instead.", error: true)] +[Obsolete("This interface has been obsoleted, use IStructuralTypeMaterializerSource instead.", error: true)] public interface IEntityMaterializerSource; diff --git a/src/EFCore/Statics.cs b/src/EFCore/Statics.cs deleted file mode 100644 index 9363ed8d35d..00000000000 --- a/src/EFCore/Statics.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Utilities; - -internal static class Statics -{ - internal static readonly bool[][] TrueArrays = - [ - [], - [true], - [true, true], - [true, true, true], - ]; - - internal static readonly bool[][] FalseArrays = - [ - [], - [false], - [false, false], - [false, false, false], - ]; - - internal static IReadOnlyList FalseTrue = [false, true]; - internal static IReadOnlyList TrueFalse = [true, false]; -} diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index 164469a01b7..3e87ab98dc6 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -48,6 +48,7 @@ public static bool IsValidEntityType(this Type type) public static bool IsValidComplexType(this Type type) => !type.IsArray && !type.IsInterface + && !type.IsNullableValueType() && !IsScalarType(type); public static bool IsScalarType(this Type type) diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index 4d8eebc8134..3823725e3e5 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -368,6 +368,8 @@ private class EntityWithTwoProperties public int AlternateId { get; set; } [NotMapped] + public List List { get; set; } + [NotMapped] public Coordinates Coordinates { get; set; } public EntityWithOneProperty EntityWithOneProperty { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/ComplexTypesTrackingRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/ComplexTypesTrackingRelationalTestBase.cs index c30a62c6688..24b1415ad05 100644 --- a/test/EFCore.Relational.Specification.Tests/ComplexTypesTrackingRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/ComplexTypesTrackingRelationalTestBase.cs @@ -29,20 +29,50 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b => { b.ComplexCollection( - e => e.Activities, b => - { - b.ToJson(); - }); + e => e.Activities, b => b.ToJson()); }); modelBuilder.Entity( b => { b.ComplexCollection( - e => e.Activities, b => - { - b.ToJson(); - }); + e => e.Activities, b => b.ToJson()); + }); + + modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ToJson()); + }); + + // TODO: Issue #31411 + //modelBuilder.Entity( + // b => + // { + // b.ComplexCollection( + // e => e.Activities, b => b.ToJson()); + // }); + + //modelBuilder.Entity( + // b => + // { + // b.ComplexCollection( + // e => e.Activities, b => b.ToJson()); + // }); + + modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ToJson()); + }); + + modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ToJson()); }); if (!UseProxies) @@ -51,20 +81,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b => { b.ComplexCollection( - e => e.Activities, b => - { - b.ToJson(); - }); + e => e.Activities, b => b.ToJson()); }); modelBuilder.Entity( b => { b.ComplexCollection( - e => e.Activities, b => - { - b.ToJson(); - }); + e => e.Activities, b => b.ToJson()); }); } } diff --git a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 8420a4f6df6..7b66fed7497 100644 --- a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -760,7 +760,7 @@ public virtual void Complex_type_discriminator_mapped_to_json_has_default_json_p b.ToJson("customer_data"); b.Ignore(c => c.Details); b.Ignore(c => c.Orders); - b.HasDiscriminator("CustomerType"); + b.HasDiscriminator("Title"); // Issue #31250 // .HasValue("Customer") // .HasValue("Special") @@ -776,7 +776,7 @@ public virtual void Complex_type_discriminator_mapped_to_json_has_default_json_p var discriminatorProperty = complexType.FindDiscriminatorProperty(); Assert.NotNull(discriminatorProperty); - Assert.Equal("CustomerType", discriminatorProperty.Name); + Assert.Equal("Title", discriminatorProperty.Name); Assert.Equal("$type", discriminatorProperty.GetJsonPropertyName()); } diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index bc949dbc559..452be5e6d18 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -166,7 +166,7 @@ await InitializeAsync( } } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual async Task Can_share_required_columns_with_complex_types() { await InitializeAsync( @@ -210,11 +210,10 @@ await InitializeAsync( using (var context = CreateContext()) { - // Requires query support for shadow properties in complex types #35613 - //var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); + var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); - //Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry - // .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); + Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry + .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); } } @@ -290,7 +289,7 @@ await InitializeAsync( } } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual async Task Can_use_optional_dependents_with_shared_concurrency_tokens_with_complex_types() { await InitializeAsync( @@ -329,45 +328,45 @@ await InitializeAsync( } // Requires query support for shadow properties in complex types #35613 - //using (var context = CreateContext()) - //{ - // var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); + using (var context = CreateContext()) + { + var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); - // Assert.Equal(1, scooter.SeatingCapacity); + Assert.Equal(1, scooter.SeatingCapacity); - // scooter.Engine = new IntermittentCombustionEngine { FuelTank = new FuelTank { Capacity = 5 } }; + scooter.Engine = new IntermittentCombustionEngine { FuelTank = new FuelTank { Capacity = 5 } }; - // var seatingCapacityEntry = context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry - // .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity"); + var seatingCapacityEntry = context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry + .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity"); - // Assert.Equal(0, seatingCapacityEntry.OriginalValue); + Assert.Equal(0, seatingCapacityEntry.OriginalValue); - // context.SaveChanges(); + context.SaveChanges(); - // Assert.Equal(0, scooter.SeatingCapacity); - // Assert.Equal(0, seatingCapacityEntry.OriginalValue); - // Assert.Equal(0, seatingCapacityEntry.CurrentValue); - //} + Assert.Equal(0, scooter.SeatingCapacity); + Assert.Equal(0, seatingCapacityEntry.OriginalValue); + Assert.Equal(0, seatingCapacityEntry.CurrentValue); + } - //using (var context = CreateContext()) - //{ - // var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); + using (var context = CreateContext()) + { + var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); - // Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry - // .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); + Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry + .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); - // scooter.SeatingCapacity = 2; - // context.SaveChanges(); - //} + scooter.SeatingCapacity = 2; + context.SaveChanges(); + } - //using (var context = CreateContext()) - //{ - // var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); + using (var context = CreateContext()) + { + var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Gas scooter"); - // Assert.Equal(2, scooter.SeatingCapacity); - // Assert.Equal(2, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry - // .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); - //} + Assert.Equal(2, scooter.SeatingCapacity); + Assert.Equal(2, context.Entry(scooter).Reference(v => (IntermittentCombustionEngine)v.Engine).TargetEntry + .ComplexProperty(v => v.FuelTank).Property("SeatingCapacity").CurrentValue); + } } protected async Task Test_roundtrip(Action onModelCreating) diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.Json.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.Json.cs index 69bafd3c689..0eec7f64454 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.Json.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.Json.cs @@ -722,6 +722,26 @@ public void Throws_when_has_json_property_name_on_non_json_mapped_complex_proper modelBuilder); } + [ConditionalFact] + public void Throws_when_nested_complex_property_mapped_to_json_with_table_sharing() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity() + .ComplexProperty(e => e.ComplexProp, outerBuilder => + { + outerBuilder.ComplexProperty(x => x.NestedComplex, innerBuilder => + { + innerBuilder.ToJson("inner_json"); + }); + }); + + VerifyError( + RelationalStrings.NestedComplexPropertyJsonWithTableSharing( + "ValidatorComplexEntity.ComplexProp#ValidatorComplexType.NestedComplex", + "ValidatorComplexEntity.ComplexProp#ValidatorComplexType"), + modelBuilder); + } + [ConditionalFact] public void Throws_when_properties_in_complex_type_have_same_json_property_name() { @@ -851,6 +871,13 @@ protected class ValidatorComplexType { public string Name { get; set; } public int Number { get; set; } + public ValidatorNestedComplexType NestedComplex { get; set; } + } + + protected class ValidatorNestedComplexType + { + public string Value { get; set; } + public int Count { get; set; } } protected class ValidatorJsonEntityBasic @@ -945,4 +972,49 @@ protected class ValidatorJsonEntityTableSplitting public int Id { get; set; } public ValidatorJsonEntityBasic Link { get; set; } } + + [ConditionalFact] + public void Throw_when_concurrency_token_configured_on_json_mapped_owned_entity() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference, bb => + { + bb.ToJson(); + bb.Property(x => x.Name).IsConcurrencyToken(); + bb.Ignore(x => x.NestedCollection); + bb.Ignore(x => x.NestedReference); + }); + b.Ignore(x => x.OwnedCollection); + }); + + VerifyError( + RelationalStrings.ConcurrencyTokenOnJsonMappedProperty("Name", nameof(ValidatorJsonOwnedRoot)), + modelBuilder); + } + + [ConditionalFact] + public void Throw_when_concurrency_token_configured_on_json_mapped_complex_property() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity( + b => + { + b.ComplexProperty(x => x.OwnedReference, cb => + { + cb.ToJson(); + cb.Property(x => x.Name).IsConcurrencyToken(); + cb.Ignore(x => x.NestedCollection); + cb.Ignore(x => x.NestedReference); + }); + }); + + VerifyError( + RelationalStrings.ConcurrencyTokenOnJsonMappedProperty("Name", "ValidatorJsonEntityBasic.OwnedReference#ValidatorJsonOwnedRoot"), + modelBuilder); + } } diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 1747d5dbca4..26450ccf5d5 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -9805,6 +9805,150 @@ public class AccountHolder { } + [ConditionalFact] + public virtual void Convert_table_from_owned_to_complex_properties_mapped_to_json() + => Execute( + builder => + { + builder.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + + e.OwnsOne( + "Owned", "json_reference", o => + { + o.ToJson(); + o.Property("Value").HasJsonPropertyName("custom_value"); + o.Property("Date").HasJsonPropertyName("custom_date"); + o.OwnsOne("Nested", "nested_reference", n => + { + n.Property("Foo"); + n.Property("Bar"); + }); + o.OwnsMany("Nested2", "nested_collection", n => + { + n.Property("Foo"); + n.Property("Bar"); + }); + }); + + e.OwnsMany( + "Owned2", "json_collection", o => + { + o.ToJson(); + o.Property("Value"); + o.Property("Date"); + o.OwnsOne("Nested3", "NestedReference2", n => + { + n.Property("Foo"); + n.Property("Bar"); + }); + o.OwnsMany("Nested4", "NestedCollection2", n => + { + n.Property("Foo"); + n.Property("Bar"); + }); + o.Property("Date2"); + }); + }); + }, + builder => + { + builder.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + + e.ComplexProperty( + "ComplexReference", cp => + { + cp.ToJson("json_reference"); + cp.Property(x => x.Value).HasJsonPropertyName("custom_value"); + cp.Property(x => x.Date).HasJsonPropertyName("custom_date"); + cp.ComplexCollection(x => x.NestedCollection, nc => + { + nc.HasJsonPropertyName("nested_collection"); + }); + cp.ComplexProperty(x => x.Nested, np => + { + np.HasJsonPropertyName("nested_reference"); + }); + }); + + e.ComplexCollection, MyJsonComplex>( + "ComplexCollection", cp => + { + cp.ToJson("json_collection"); + cp.Property(x => x.Value); + cp.Property(x => x.Date); + cp.ComplexCollection(x => x.NestedCollection, nc => + { + nc.HasJsonPropertyName("nested_collection2"); + }); + cp.ComplexProperty(x => x.Nested, np => + { + np.HasJsonPropertyName("nested_reference2"); + }); + }); + }); + }, + Assert.Empty); + + [ConditionalFact] + public virtual void Noop_on_complex_properties() => Execute( + builder => + { + builder.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + e.Property("Name"); + + e.ComplexProperty( + "ComplexReference", cp => + { + cp.IsRequired(false); + cp.Property(x => x.Value).HasJsonPropertyName("custom_value"); + cp.Property(x => x.Date).HasJsonPropertyName("custom_date"); + cp.ComplexCollection(x => x.NestedCollection).ToJson(); + cp.ComplexProperty(x => x.Nested); + }); + + e.ComplexCollection, MyJsonComplex>( + "ComplexCollection", cp => + { + cp.ToJson(); + cp.Property(x => x.Value); + cp.Property(x => x.Date); + cp.ComplexCollection(x => x.NestedCollection); + cp.ComplexProperty(x => x.Nested); + }); + }); + }, + source => { }, + target => { }, + Assert.Empty); + + protected class MyJsonComplex + { + public string Value { get; set; } + public DateTime Date { get; set; } + public MyNestedComplex Nested { get; set; } + public List NestedCollection { get; set; } + } + + protected class MyNestedComplex + { + public int Foo { get; set; } + public DateTime Bar { get; set; } + } + [ConditionalFact] public void SeedData_with_guid_AK_and_multiple_owned_types() => Execute( diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs index 24c0c295def..bc2d2874f68 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestRelationalTypeMappingSource.cs @@ -108,7 +108,8 @@ private readonly IReadOnlyDictionary _simpleMapping { typeof(decimal), _defaultDecimalMapping }, { typeof(TimeSpan), _defaultTimeSpanMapping }, { typeof(string), _string }, - { typeof(int[]), _intArray } + { typeof(int[]), _intArray }, + { typeof(JsonTypePlaceholder), _jsonMapping } }; private readonly IReadOnlyDictionary _simpleNameMappings diff --git a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs index 43e3a666102..160df915116 100644 --- a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs @@ -490,6 +490,156 @@ public virtual void Can_read_original_values_for_properties_of_complex_readonly_ public virtual void Can_write_original_values_for_properties_of_complex_readonly_struct_collections_with_fields(bool trackFromQuery) => WriteOriginalValuesTest(trackFromQuery, CreateFieldCollectionPubWithReadonlyStructs); + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Added, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Deleted, false)] + [InlineData(EntityState.Deleted, true)] + public virtual Task Can_track_entity_with_complex_type_array_collections(EntityState state, bool async) + => TrackAndSaveTest(state, async, CreatePubWithArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_mark_complex_type_array_collection_properties_modified(bool trackFromQuery) + => MarkModifiedTest(trackFromQuery, CreatePubWithArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_read_original_values_for_properties_of_complex_type_array_collections(bool trackFromQuery) + => ReadOriginalValuesTest(trackFromQuery, CreatePubWithArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_write_original_values_for_properties_of_complex_type_array_collections(bool trackFromQuery) + => WriteOriginalValuesTest(trackFromQuery, CreatePubWithArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Added, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Deleted, false)] + [InlineData(EntityState.Deleted, true)] + public virtual Task Can_track_entity_with_complex_struct_array_collections(EntityState state, bool async) + => TrackAndSaveTest(state, async, CreatePubWithStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_mark_complex_struct_array_collection_properties_modified(bool trackFromQuery) + => MarkModifiedTest(trackFromQuery, CreatePubWithStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_read_original_values_for_properties_of_complex_struct_array_collections(bool trackFromQuery) + => ReadOriginalValuesTest(trackFromQuery, CreatePubWithStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_write_original_values_for_properties_of_complex_struct_array_collections(bool trackFromQuery) + => WriteOriginalValuesTest(trackFromQuery, CreatePubWithStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #31411")] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Added, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Deleted, false)] + [InlineData(EntityState.Deleted, true)] + public virtual Task Can_track_entity_with_complex_readonly_struct_array_collections(EntityState state, bool async) + => TrackAndSaveTest(state, async, CreatePubWithReadonlyStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #31411")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_mark_complex_readonly_struct_array_collection_properties_modified(bool trackFromQuery) + => MarkModifiedTest(trackFromQuery, CreatePubWithReadonlyStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #31411")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_read_original_values_for_properties_of_complex_readonly_struct_array_collections(bool trackFromQuery) + => ReadOriginalValuesTest(trackFromQuery, CreatePubWithReadonlyStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #31411")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_write_original_values_for_properties_of_complex_readonly_struct_array_collections(bool trackFromQuery) + => WriteOriginalValuesTest(trackFromQuery, CreatePubWithReadonlyStructArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Added, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Deleted, false)] + [InlineData(EntityState.Deleted, true)] + public virtual Task Can_track_entity_with_complex_record_array_collections(EntityState state, bool async) + => TrackAndSaveTest(state, async, CreatePubWithRecordArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_mark_complex_record_array_collection_properties_modified(bool trackFromQuery) + => MarkModifiedTest(trackFromQuery, CreatePubWithRecordArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_read_original_values_for_properties_of_complex_record_array_collections(bool trackFromQuery) + => ReadOriginalValuesTest(trackFromQuery, CreatePubWithRecordArrayCollections); + + [ConditionalTheory(Skip = "Issue #36483")] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_write_original_values_for_properties_of_complex_record_array_collections(bool trackFromQuery) + => WriteOriginalValuesTest(trackFromQuery, CreatePubWithRecordArrayCollections); + + [ConditionalTheory] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Added, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Deleted, false)] + [InlineData(EntityState.Deleted, true)] + public virtual Task Can_track_entity_with_complex_property_bag_collections(EntityState state, bool async) + => TrackAndSaveTest(state, async, CreatePubWithPropertyBagCollections); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_mark_complex_property_bag_collection_properties_modified(bool trackFromQuery) + => MarkModifiedTest(trackFromQuery, CreatePubWithPropertyBagCollections); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_read_original_values_for_properties_of_complex_property_bag_collections(bool trackFromQuery) + => ReadOriginalValuesTest(trackFromQuery, CreatePubWithPropertyBagCollections); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual void Can_write_original_values_for_properties_of_complex_property_bag_collections(bool trackFromQuery) + => WriteOriginalValuesTest(trackFromQuery, CreatePubWithPropertyBagCollections); + private async Task TrackAndSaveTest(EntityState state, bool async, Func createPub) where TEntity : class => await ExecuteWithStrategyInTransactionAsync( @@ -2322,10 +2472,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b => { b.ComplexCollection( - e => e.Activities, b => - { - b.ComplexCollection(e => e.Teams); - }); + e => e.Activities, b => b.ComplexCollection(e => e.Teams)); b.ComplexProperty(e => e.FeaturedTeam); }); @@ -2354,14 +2501,75 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con // }); modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ComplexCollection(e => e.Teams)); + b.ComplexProperty(e => e.FeaturedTeam); + }); + + modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ComplexCollection(e => e.Teams)); + b.ComplexProperty(e => e.FeaturedTeam); + }); + + // TODO: Issue #31411 + //modelBuilder.Entity( + // b => + // { + // b.ComplexCollection( + // e => e.Activities, b => + // { + // b.ComplexCollection(e => e.Teams); + // }); + // b.ComplexProperty(e => e.FeaturedTeam); + // }); + + //modelBuilder.Entity( + // b => + // { + // b.ComplexCollection( + // e => e.Activities, b => + // { + // b.ComplexCollection(e => e.Teams); + // }); + // b.ComplexProperty(e => e.FeaturedTeam); + // }); + + modelBuilder.Entity( + b => + { + b.ComplexCollection( + e => e.Activities, b => b.ComplexCollection(e => e.Teams)); + b.ComplexProperty(e => e.FeaturedTeam); + }); + + modelBuilder.Entity( b => { b.ComplexCollection( e => e.Activities, b => { - b.ComplexCollection(e => e.Teams); + b.ComplexCollection(e => e.Teams, "TeamPropertyBag", teamBuilder => + { + teamBuilder.Property("Name"); + teamBuilder.Property>("Members"); + teamBuilder.Property("Founded"); + teamBuilder.Property("IsActive"); + teamBuilder.Property("Rating"); + }); }); - b.ComplexProperty(e => e.FeaturedTeam); + b.ComplexProperty(e => e.FeaturedTeam, "FeaturedTeamPropertyBag", featuredTeamBuilder => + { + featuredTeamBuilder.Property("Name"); + featuredTeamBuilder.Property>("Members"); + featuredTeamBuilder.Property("Founded"); + featuredTeamBuilder.Property("IsActive"); + featuredTeamBuilder.Property("Rating"); + }); }); if (!UseProxies) @@ -2773,6 +2981,61 @@ public record FieldActivityRecordWithCollection public List Teams = null!; } + public class ActivityWithArrayCollection + { + public string Name { get; set; } = null!; + public decimal? CoverCharge { get; set; } + public bool IsTeamBased { get; set; } + public string? Description { get; set; } + public string[]? Notes { get; set; } + public DayOfWeek Day { get; set; } + public Team[] Teams { get; set; } = []; + } + + public struct ActivityStructWithArrayCollection + { + public string Name { get; set; } + public decimal? CoverCharge { get; set; } + public bool IsTeamBased { get; set; } + public string? Description { get; set; } + public string[]? Notes { get; set; } + public DayOfWeek Day { get; set; } + public TeamStruct[] Teams { get; set; } + } + + public readonly struct ActivityReadonlyStructWithArrayCollection + { + public string Name { get; init; } + public decimal? CoverCharge { get; init; } + public bool IsTeamBased { get; init; } + public string? Description { get; init; } + public string[]? Notes { get; init; } + public DayOfWeek Day { get; init; } + public TeamReadonlyStruct[] Teams { get; init; } + } + + public record ActivityRecordWithArrayCollection + { + public string Name { get; init; } = null!; + public decimal? CoverCharge { get; init; } + public bool IsTeamBased { get; init; } + public string? Description { get; init; } + public string[]? Notes { get; init; } + public DayOfWeek Day { get; init; } + public TeamRecord[] Teams { get; init; } = []; + } + + public class ActivityWithPropertyBagCollection + { + public string Name { get; set; } = null!; + public decimal? CoverCharge { get; set; } + public bool IsTeamBased { get; set; } + public string? Description { get; set; } + public string[]? Notes { get; set; } + public DayOfWeek Day { get; set; } + public List> Teams { get; set; } = []; + } + protected PubWithReadonlyStructs CreatePubWithReadonlyStructs(DbContext context) { @@ -3000,6 +3263,46 @@ public class PubWithRecordCollections public virtual TeamRecord FeaturedTeam { get; set; } = null!; } + public class PubWithArrayCollections + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual ActivityWithArrayCollection[] Activities { get; set; } = []; + public virtual Team FeaturedTeam { get; set; } = null!; + } + + public class PubWithStructArrayCollections + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual ActivityStructWithArrayCollection[] Activities { get; set; } = []; + public virtual TeamStruct FeaturedTeam { get; set; } + } + + public class PubWithReadonlyStructArrayCollections + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual ActivityReadonlyStructWithArrayCollection[] Activities { get; set; } = []; + public virtual TeamReadonlyStruct FeaturedTeam { get; set; } + } + + public class PubWithRecordArrayCollections + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual ActivityRecordWithArrayCollection[] Activities { get; set; } = []; + public virtual TeamRecord FeaturedTeam { get; set; } = null!; + } + + public class PubWithPropertyBagCollections + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } = null!; + public virtual List Activities { get; set; } = []; + public virtual Dictionary FeaturedTeam { get; set; } = null!; + } + public class FieldPubWithCollections { public Guid Id { get; set; } @@ -3770,6 +4073,368 @@ protected PubWithRecordCollections CreatePubWithRecordCollections(DbContext cont return pub; } + protected PubWithArrayCollections CreatePubWithArrayCollections(DbContext context) + { + var pub = Fixture.UseProxies + ? context.CreateProxy() + : new PubWithArrayCollections(); + + pub.Id = Guid.NewGuid(); + pub.Name = "The FBI"; + + pub.Activities = + [ + new ActivityWithArrayCollection + { + Name = "Pub Quiz", + Day = DayOfWeek.Monday, + Description = "A general knowledge pub quiz.", + Notes = ["One", "Two", "Three"], + CoverCharge = 2.0m, + IsTeamBased = true, + Teams = + [ + new Team + { + Name = "Clueless", + Members = + { + "Boris", + "David", + "Theresa" + } + }, + new Team + { + Name = "ZZ", + Members = + { + "Has Beard", + "Has Beard", + "Is Called Beard" + } + } + ] + }, + new ActivityWithArrayCollection + { + Name = "Music Quiz", + Day = DayOfWeek.Friday, + Description = "A music pub quiz.", + Notes = [], + CoverCharge = 5.0m, + IsTeamBased = true, + Teams = + [ + new Team + { + Name = "Dazed and Confused", + Members = + { + "Robert", + "Jimmy", + "John", + "Jason" + } + }, + new Team { Name = "Banksy", Members = [] } + ] + } + ]; + + pub.FeaturedTeam = new Team { Name = "Not In This Lifetime", Members = { "Slash", "Axl" } }; + + return pub; + } + + protected PubWithStructArrayCollections CreatePubWithStructArrayCollections(DbContext context) + { + var pub = Fixture.UseProxies + ? context.CreateProxy() + : new PubWithStructArrayCollections(); + + pub.Id = Guid.NewGuid(); + pub.Name = "The FBI"; + + pub.Activities = + [ + new ActivityStructWithArrayCollection + { + Name = "Pub Quiz", + Day = DayOfWeek.Monday, + Description = "A general knowledge pub quiz.", + Notes = ["One", "Two", "Three"], + CoverCharge = 2.0m, + IsTeamBased = true, + Teams = + [ + new TeamStruct + { + Name = "Clueless", + Members = + [ + "Boris", + "David", + "Theresa" + ] + }, + new TeamStruct + { + Name = "ZZ", + Members = + [ + "Has Beard", + "Has Beard", + "Is Called Beard" + ] + } + ] + }, + new ActivityStructWithArrayCollection + { + Name = "Music Quiz", + Day = DayOfWeek.Friday, + Description = "A music pub quiz.", + Notes = [], + CoverCharge = 5.0m, + IsTeamBased = true, + Teams = + [ + new TeamStruct + { + Name = "Dazed and Confused", + Members = + [ + "Robert", + "Jimmy", + "John", + "Jason" + ] + }, + new TeamStruct { Name = "Banksy", Members = [] } + ] + } + ]; + + pub.FeaturedTeam = new TeamStruct { Name = "Not In This Lifetime", Members = ["Slash", "Axl"] }; + + return pub; + } + + protected PubWithReadonlyStructArrayCollections CreatePubWithReadonlyStructArrayCollections(DbContext context) + { + var pub = Fixture.UseProxies + ? context.CreateProxy() + : new PubWithReadonlyStructArrayCollections(); + + pub.Id = Guid.NewGuid(); + pub.Name = "The FBI"; + + pub.Activities = + [ + new ActivityReadonlyStructWithArrayCollection + { + Name = "Pub Quiz", + Day = DayOfWeek.Monday, + Description = "A general knowledge pub quiz.", + Notes = ["One", "Two", "Three"], + CoverCharge = 2.0m, + IsTeamBased = true, + Teams = + [ + new TeamReadonlyStruct("Clueless", [ + "Boris", + "David", + "Theresa" + ]), + new TeamReadonlyStruct("ZZ", [ + "Has Beard", + "Has Beard", + "Is Called Beard" + ]) + ] + }, + new ActivityReadonlyStructWithArrayCollection + { + Name = "Music Quiz", + Day = DayOfWeek.Friday, + Description = "A music pub quiz.", + Notes = [], + CoverCharge = 5.0m, + IsTeamBased = true, + Teams = + [ + new TeamReadonlyStruct("Dazed and Confused", [ + "Robert", + "Jimmy", + "John", + "Jason" + ]), + new TeamReadonlyStruct("Banksy", []) + ] + } + ]; + + pub.FeaturedTeam = new TeamReadonlyStruct("Not In This Lifetime", ["Slash", "Axl"]); + + return pub; + } + + protected PubWithRecordArrayCollections CreatePubWithRecordArrayCollections(DbContext context) + { + var pub = Fixture.UseProxies + ? context.CreateProxy() + : new PubWithRecordArrayCollections(); + + pub.Id = Guid.NewGuid(); + pub.Name = "The FBI"; + + pub.Activities = + [ + new ActivityRecordWithArrayCollection + { + Name = "Pub Quiz", + Day = DayOfWeek.Monday, + Description = "A general knowledge pub quiz.", + Notes = ["One", "Two", "Three"], + CoverCharge = 2.0m, + IsTeamBased = true, + Teams = + [ + new TeamRecord + { + Name = "Clueless", + Members = + [ + "Boris", + "David", + "Theresa" + ] + }, + new TeamRecord + { + Name = "ZZ", + Members = + [ + "Has Beard", + "Has Beard", + "Is Called Beard" + ] + } + ] + }, + new ActivityRecordWithArrayCollection + { + Name = "Music Quiz", + Day = DayOfWeek.Friday, + Description = "A music pub quiz.", + Notes = [], + CoverCharge = 5.0m, + IsTeamBased = true, + Teams = + [ + new TeamRecord + { + Name = "Dazed and Confused", + Members = + [ + "Robert", + "Jimmy", + "John", + "Jason" + ] + }, + new TeamRecord { Name = "Banksy", Members = [] } + ] + } + ]; + + pub.FeaturedTeam = new TeamRecord { Name = "Not In This Lifetime", Members = ["Slash", "Axl"] }; + + return pub; + } + + protected PubWithPropertyBagCollections CreatePubWithPropertyBagCollections(DbContext context) + { + var pub = Fixture.UseProxies + ? context.CreateProxy() + : new PubWithPropertyBagCollections(); + + pub.Id = Guid.NewGuid(); + pub.Name = "The FBI"; + + pub.Activities = + [ + new ActivityWithPropertyBagCollection + { + Name = "Pub Quiz", + Day = DayOfWeek.Monday, + Description = "A general knowledge pub quiz.", + Notes = ["One", "Two", "Three"], + CoverCharge = 2.0m, + IsTeamBased = true, + Teams = + [ + new Dictionary + { + ["Name"] = "Clueless", + ["Members"] = new List { "Boris", "David", "Theresa" }, + ["Founded"] = new DateTime(2015, 3, 15), + ["IsActive"] = true, + ["Rating"] = 4.2 + }, + new Dictionary + { + ["Name"] = "ZZ", + ["Members"] = new List { "Has Beard", "Has Beard", "Is Called Beard" }, + ["Founded"] = new DateTime(2020, 1, 1), + ["IsActive"] = false, + ["Rating"] = 3.8 + } + ] + }, + new ActivityWithPropertyBagCollection + { + Name = "Music Quiz", + Day = DayOfWeek.Friday, + Description = "A music pub quiz.", + Notes = [], + CoverCharge = 5.0m, + IsTeamBased = true, + Teams = + [ + new Dictionary + { + ["Name"] = "Dazed and Confused", + ["Members"] = new List { "Robert", "Jimmy", "John", "Jason" }, + ["Founded"] = new DateTime(2018, 7, 20), + ["IsActive"] = true, + ["Rating"] = 4.9 + }, + new Dictionary + { + ["Name"] = "Banksy", + ["Members"] = new List(), + ["Founded"] = new DateTime(2022, 12, 31), + ["IsActive"] = true, + ["Rating"] = 2.1 + } + ] + } + ]; + + pub.FeaturedTeam = new Dictionary + { + ["Name"] = "Not In This Lifetime", + ["Members"] = new List { "Slash", "Axl" }, + ["Founded"] = new DateTime(2016, 4, 1), + ["IsActive"] = true, + ["Rating"] = 4.7 + }; + + return pub; + } + protected static FieldPubWithCollections CreateFieldCollectionPub(DbContext context) => new() { diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs index 6f7353be740..63a12cc35ce 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexCollections.cs @@ -117,7 +117,7 @@ public virtual void Can_set_property_annotation_by_type() Assert.Equal("bar", property["foo"]); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() { var modelBuilder = CreateModelBuilder(); @@ -219,7 +219,7 @@ public virtual void Can_ignore_shadow_properties_when_they_have_been_added_expli Assert.Null(complexType.FindProperty("Shadow")); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_add_shadow_properties_when_they_have_been_ignored() { var modelBuilder = CreateModelBuilder(); @@ -246,7 +246,7 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored() Assert.NotNull(complexType.FindProperty("Shadow")); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_made_required() { var modelBuilder = CreateModelBuilder(); @@ -279,7 +279,7 @@ public virtual void Properties_can_be_made_required() Assert.False(complexType.FindProperty("Bottom").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_made_optional() { var modelBuilder = CreateModelBuilder(); @@ -309,7 +309,7 @@ public virtual void Properties_can_be_made_optional() Assert.True(complexType.FindProperty("Bottom").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Non_nullable_properties_cannot_be_made_optional() { var modelBuilder = CreateModelBuilder(); @@ -344,7 +344,7 @@ public virtual void Non_nullable_properties_cannot_be_made_optional() Assert.False(complexType.FindProperty("Top").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() { var modelBuilder = CreateModelBuilder(); @@ -379,7 +379,7 @@ public virtual void Properties_specified_by_string_are_shadow_properties_unless_ Assert.NotEqual(complexType.FindProperty("Gluon").GetShadowIndex(), complexType.FindProperty("Photon").GetShadowIndex()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_access_mode_set() { var modelBuilder = CreateModelBuilder(); @@ -456,7 +456,7 @@ public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels( Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_provider_type_set() => Properties_can_have_provider_type_set(); @@ -510,7 +510,7 @@ protected virtual void Properties_can_have_provider_type_set() Assert.True(top.GetProviderValueComparer() is ValueComparer); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_provider_type_set_for_type() { var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); @@ -539,7 +539,7 @@ public virtual void Properties_can_have_provider_type_set_for_type() Assert.Same(typeof(byte[]), complexType.FindProperty("Strange")!.GetProviderClrType()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_non_generic_value_converter_set() => Properties_can_have_non_generic_value_converter_set(); @@ -584,7 +584,7 @@ protected virtual void Properties_can_have_non_generic_value_converter_set Properties_can_have_custom_type_value_converter_type_set(); @@ -638,7 +638,7 @@ protected class UTF8StringToBytesConverter() : StringToBytesConverter(Encoding.U protected class CustomValueComparer() : ValueComparer(false); - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_value_converter_set_inline() { var modelBuilder = CreateModelBuilder(); @@ -684,7 +684,7 @@ public virtual void Properties_can_have_value_converter_set_inline() Assert.IsType>(strange.GetProviderValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_value_converter_set() { var modelBuilder = CreateModelBuilder(); @@ -733,7 +733,7 @@ public virtual void Properties_can_have_value_converter_set() Assert.IsType>(strange.GetProviderValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Value_converter_configured_on_non_nullable_type_is_applied() { var modelBuilder = CreateModelBuilder( @@ -766,7 +766,7 @@ public virtual void Value_converter_configured_on_non_nullable_type_is_applied() Assert.IsType>(wierd.GetValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() { var modelBuilder = CreateModelBuilder( @@ -903,7 +903,7 @@ protected virtual void Throws_for_incompatible_type() .ComplexCollection, Customer>(nameof(ComplexProperties.Customer))).Message); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_max_length_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -940,7 +940,7 @@ public virtual void Can_set_max_length_for_property_type() Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_sentinel_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -974,7 +974,7 @@ public virtual void Can_set_sentinel_for_properties() Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_sentinel_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1011,7 +1011,7 @@ public virtual void Can_set_sentinel_for_property_type() Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unbounded_max_length_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1048,7 +1048,7 @@ public virtual void Can_set_unbounded_max_length_for_property_type() Assert.Equal(-1, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_precision_and_scale_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1092,7 +1092,7 @@ public virtual void Can_set_precision_and_scale_for_property_type() Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_custom_value_generator_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1200,7 +1200,7 @@ protected class StringCollectionEntity public ICollection Property { get; set; } } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unicode_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1234,7 +1234,7 @@ public virtual void Can_set_unicode_for_properties() Assert.False(complexType.FindProperty("Bottom").IsUnicode()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unicode_for_property_type() { var modelBuilder = CreateModelBuilder( diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs index 2d9020023e5..1b8d6063d4c 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.ComplexType.cs @@ -115,7 +115,7 @@ public virtual void Can_set_property_annotation_by_type() Assert.Equal("bar", property["foo"]); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() { var modelBuilder = CreateModelBuilder(); @@ -213,7 +213,7 @@ public virtual void Can_ignore_shadow_properties_when_they_have_been_added_expli Assert.Null(complexType.FindProperty("Shadow")); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_add_shadow_properties_when_they_have_been_ignored() { var modelBuilder = CreateModelBuilder(); @@ -239,7 +239,7 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored() Assert.NotNull(complexType.FindProperty("Shadow")); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_made_required() { var modelBuilder = CreateModelBuilder(); @@ -271,7 +271,7 @@ public virtual void Properties_can_be_made_required() Assert.False(complexType.FindProperty("Bottom").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_made_optional() { var modelBuilder = CreateModelBuilder(); @@ -297,7 +297,7 @@ public virtual void Properties_can_be_made_optional() Assert.True(complexType.FindProperty("Bottom").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Non_nullable_properties_cannot_be_made_optional() { var modelBuilder = CreateModelBuilder(); @@ -331,7 +331,7 @@ public virtual void Non_nullable_properties_cannot_be_made_optional() Assert.False(complexType.FindProperty("Top").IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() { var modelBuilder = CreateModelBuilder(); @@ -365,7 +365,7 @@ public virtual void Properties_specified_by_string_are_shadow_properties_unless_ Assert.NotEqual(complexType.FindProperty("Gluon").GetShadowIndex(), complexType.FindProperty("Photon").GetShadowIndex()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_made_concurrency_tokens() { var modelBuilder = CreateModelBuilder(); @@ -410,7 +410,7 @@ public virtual void Properties_can_be_made_concurrency_tokens() Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, complexType.GetChangeTrackingStrategy()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_access_mode_set() { var modelBuilder = CreateModelBuilder(); @@ -507,7 +507,7 @@ public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels( Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up")!.GetPropertyAccessMode()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_provider_type_set() => Properties_can_have_provider_type_set(); @@ -560,7 +560,7 @@ protected virtual void Properties_can_have_provider_type_set() Assert.True(top.GetProviderValueComparer() is ValueComparer); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_provider_type_set_for_type() { var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); @@ -588,7 +588,7 @@ public virtual void Properties_can_have_provider_type_set_for_type() Assert.Same(typeof(byte[]), complexType.FindProperty("Strange")!.GetProviderClrType()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_non_generic_value_converter_set() => Properties_can_have_non_generic_value_converter_set(); @@ -632,7 +632,7 @@ protected virtual void Properties_can_have_non_generic_value_converter_set Properties_can_have_custom_type_value_converter_type_set(); @@ -685,7 +685,7 @@ protected class UTF8StringToBytesConverter() : StringToBytesConverter(Encoding.U protected class CustomValueComparer() : ValueComparer(false); - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_value_converter_set_inline() { var modelBuilder = CreateModelBuilder(); @@ -730,7 +730,7 @@ public virtual void Properties_can_have_value_converter_set_inline() Assert.IsType>(strange.GetProviderValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_have_value_converter_set() { var modelBuilder = CreateModelBuilder(); @@ -849,7 +849,7 @@ public virtual void Properties_can_have_value_converter_configured_by_type() Assert.IsType>(wrappedProperty.GetValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Value_converter_configured_on_non_nullable_type_is_applied() { var modelBuilder = CreateModelBuilder( @@ -881,7 +881,7 @@ public virtual void Value_converter_configured_on_non_nullable_type_is_applied() Assert.IsType>(wierd.GetValueComparer()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() { var modelBuilder = CreateModelBuilder( @@ -1017,7 +1017,7 @@ protected virtual void Throws_for_incompatible_type() .ComplexProperty>(nameof(ComplexProperties.Customer))).Message); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_be_set_to_generate_values_on_Add() { var modelBuilder = CreateModelBuilder(); @@ -1050,7 +1050,7 @@ public virtual void Properties_can_be_set_to_generate_values_on_Add() Assert.Equal(ValueGenerated.OnUpdate, complexType.FindProperty("Bottom").ValueGenerated); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Properties_can_set_row_version() { var modelBuilder = CreateModelBuilder(); @@ -1081,7 +1081,7 @@ public virtual void Properties_can_set_row_version() Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_max_length_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1114,7 +1114,7 @@ public virtual void Can_set_max_length_for_properties() Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_max_length_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1150,7 +1150,7 @@ public virtual void Can_set_max_length_for_property_type() Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_sentinel_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1183,7 +1183,7 @@ public virtual void Can_set_sentinel_for_properties() Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_sentinel_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1219,7 +1219,7 @@ public virtual void Can_set_sentinel_for_property_type() Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unbounded_max_length_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1255,7 +1255,7 @@ public virtual void Can_set_unbounded_max_length_for_property_type() Assert.Equal(-1, complexType.FindProperty("Bottom").GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_precision_and_scale_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1295,7 +1295,7 @@ public virtual void Can_set_precision_and_scale_for_properties() Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_precision_and_scale_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1338,7 +1338,7 @@ public virtual void Can_set_precision_and_scale_for_property_type() Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_custom_value_generator_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1445,7 +1445,7 @@ protected class StringCollectionEntity public ICollection Property { get; set; } } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unicode_for_properties() { var modelBuilder = CreateModelBuilder(); @@ -1478,7 +1478,7 @@ public virtual void Can_set_unicode_for_properties() Assert.False(complexType.FindProperty("Bottom").IsUnicode()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unicode_for_property_type() { var modelBuilder = CreateModelBuilder( @@ -1737,7 +1737,7 @@ protected virtual void Mapping_throws_for_empty_complex_types() Assert.Throws(modelBuilder.FinalizeModel).Message); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_primitive_collection_annotation_when_no_clr_property() { var modelBuilder = CreateModelBuilder(); @@ -1759,7 +1759,7 @@ public virtual void Can_set_primitive_collection_annotation_when_no_clr_property Assert.Equal("bar", property["foo"]); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_are_required_by_default_only_if_CLR_type_is_nullable() { var modelBuilder = CreateModelBuilder(); @@ -1806,7 +1806,7 @@ public virtual void Can_ignore_shadow_primitive_collections_when_they_have_been_ Assert.Null(complexType.FindProperty("Shadow")); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_add_shadow_primitive_collections_when_they_have_been_ignored() { var modelBuilder = CreateModelBuilder(); @@ -1831,8 +1831,7 @@ public virtual void Can_add_shadow_primitive_collections_when_they_have_been_ign var complexType = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single().ComplexType; Assert.NotNull(complexType.FindProperty("Shadow")); } - - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_can_be_made_required() { var modelBuilder = CreateModelBuilder(); @@ -1860,7 +1859,7 @@ public virtual void Primitive_collections_can_be_made_required() Assert.False(complexType.FindProperty("Strange")!.IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_can_be_made_optional() { var modelBuilder = CreateModelBuilder(); @@ -1888,7 +1887,7 @@ public virtual void Primitive_collections_can_be_made_optional() Assert.True(complexType.FindProperty("Strange")!.IsNullable); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() { var modelBuilder = CreateModelBuilder(); @@ -1921,7 +1920,7 @@ public virtual void Primitive_collections_specified_by_string_are_shadow_propert Assert.NotEqual(-1, complexType.FindProperty("Strange")!.GetShadowIndex()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_can_be_made_concurrency_tokens() { var modelBuilder = CreateModelBuilder(); @@ -2015,7 +2014,7 @@ public virtual void HasField_for_primitive_collection_throws_if_field_is_wrong_t }); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Primitive_collections_can_be_set_to_generate_values_on_Add() { var modelBuilder = CreateModelBuilder(); @@ -2047,7 +2046,7 @@ public virtual void Primitive_collections_can_be_set_to_generate_values_on_Add() Assert.Equal(ValueGenerated.OnUpdate, complexType.FindProperty("Bottom")!.ValueGenerated); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_max_length_for_primitive_collections() { var modelBuilder = CreateModelBuilder(); @@ -2079,7 +2078,7 @@ public virtual void Can_set_max_length_for_primitive_collections() Assert.Equal(100, complexType.FindProperty("Bottom")!.GetMaxLength()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_sentinel_for_primitive_collections() { var modelBuilder = CreateModelBuilder(); @@ -2112,7 +2111,7 @@ public virtual void Can_set_sentinel_for_primitive_collections() Assert.Equal(new List { "" }, complexType.FindProperty("Bottom")!.Sentinel); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_custom_value_generator_for_primitive_collections() { var modelBuilder = CreateModelBuilder(); @@ -2160,7 +2159,7 @@ public virtual void Throws_for_primitive_collection_with_bad_value_generator_typ }); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_set_unicode_for_primitive_collections() { var modelBuilder = CreateModelBuilder(); @@ -2250,7 +2249,7 @@ public virtual void Can_call_PrimitiveCollection_on_a_field() Assert.NotNull(property.FieldInfo); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_specify_discriminator_without_explicit_value() { var modelBuilder = CreateModelBuilder(); @@ -2274,7 +2273,7 @@ public virtual void Can_specify_discriminator_without_explicit_value() Assert.NotNull(discriminator.GetValueGeneratorFactory()); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #35613")] public virtual void Can_specify_discriminator_value() { var modelBuilder = CreateModelBuilder(); diff --git a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs index d9e4670cc5d..e8b6cf304e8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs @@ -318,6 +318,25 @@ public override void Can_handle_empty_nested_teams_in_complex_type_collections(b { } + // Issue #36175: Complex types with notification change tracking are not supported + public override void Can_mark_complex_property_bag_collection_properties_modified(bool trackFromQuery) + { + } + + // Issue #36175: Complex types with notification change tracking are not supported + public override void Can_read_original_values_for_properties_of_complex_property_bag_collections(bool trackFromQuery) + { + } + + // Issue #36175: Complex types with notification change tracking are not supported + public override Task Can_track_entity_with_complex_property_bag_collections(EntityState state, bool async) + => Task.CompletedTask; + + // Issue #36175: Complex types with notification change tracking are not supported + public override void Can_write_original_values_for_properties_of_complex_property_bag_collections(bool trackFromQuery) + { + } + public class SqlServerFixture : SqlServerFixtureBase { protected override string StoreName diff --git a/test/EFCore.SqlServer.FunctionalTests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.FunctionalTests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs index c30e69f2306..5d49a33d76d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -3,6 +3,8 @@ // ReSharper disable InconsistentNaming +using Xunit.Sdk; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public class SqlServerModelBuilderNonGenericTest : SqlServerModelBuilderTestBase diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 02487eb760f..13940b8e157 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -1225,6 +1225,23 @@ public virtual void Detects_shadow_properties_on_value_type_complex_types() modelBuilder); } + [ConditionalFact] + public virtual void Detects_shadow_properties_on_complex_types() + { + var modelBuilder = CreateConventionlessModelBuilder(); + var model = modelBuilder.Model; + var entityType = model.AddEntityType(typeof(SampleEntity)); + entityType.AddProperty(nameof(SampleEntity.Id), typeof(int)); + + var complexProperty = entityType.AddComplexProperty("ReferencedEntity", typeof(ReferencedEntity), typeof(ReferencedEntity)); + + complexProperty.ComplexType.AddProperty("ShadowProperty", typeof(string)); + + VerifyError( + CoreStrings.ComplexTypeShadowProperty("SampleEntity.ReferencedEntity#ReferencedEntity", "ShadowProperty"), + modelBuilder); + } + [ConditionalFact] public virtual void Detects_indexer_complex_properties() { @@ -2023,7 +2040,7 @@ public virtual void Detects_missing_complex_type_discriminator_values() { var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().ComplexProperty(b => b.A) - .HasDiscriminator("Type"); + .HasDiscriminator("P0"); VerifyError(CoreStrings.NoDiscriminatorValue("B.A#A"), modelBuilder); } @@ -2033,11 +2050,11 @@ public virtual void Detects_incompatible_complex_type_discriminator_value() { var modelBuilder = CreateConventionModelBuilder(); var complexPropertyBuilder = modelBuilder.Entity().ComplexProperty(b => b.A); - complexPropertyBuilder.HasDiscriminator("Type"); + complexPropertyBuilder.HasDiscriminator("P0"); complexPropertyBuilder.Metadata.ComplexType.SetDiscriminatorValue("1"); - VerifyError(CoreStrings.DiscriminatorValueIncompatible("1", "B.A#A", "byte"), modelBuilder); + VerifyError(CoreStrings.DiscriminatorValueIncompatible("1", "B.A#A", "int?"), modelBuilder); } [ConditionalFact]