From 76ff7a3acca089fa087bd98db6fcac8f2f057794 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Tue, 12 Sep 2023 19:20:37 +0100 Subject: [PATCH] Model validation for primitive collections (#31680) --- ...yableMethodTranslatingExpressionVisitor.cs | 13 +- ...sitor.ShaperProcessingExpressionVisitor.cs | 2 +- ...yableMethodTranslatingExpressionVisitor.cs | 5 +- .../Internal/SqlServerUpdateSqlGenerator.cs | 3 +- ...yableMethodTranslatingExpressionVisitor.cs | 5 +- .../Internal/SqliteUpdateSqlGenerator.cs | 3 +- src/EFCore/ChangeTracking/ListComparer.cs | 2 - .../NullableValueTypeListComparer.cs | 2 - .../ChangeTracking/ObjectListComparer.cs | 2 - src/EFCore/Infrastructure/ModelValidator.cs | 38 ++++ .../Conventions/ElementMappingConvention.cs | 55 +++++ .../ProviderConventionSetBuilder.cs | 1 + .../Conventions/RuntimeModelConvention.cs | 11 +- .../Internal/InternalPropertyBuilder.cs | 2 + src/EFCore/Metadata/Internal/Property.cs | 3 +- src/EFCore/Metadata/RuntimeProperty.cs | 6 +- src/EFCore/Properties/CoreStrings.Designer.cs | 8 +- src/EFCore/Properties/CoreStrings.resx | 2 +- .../Internal/EntityMaterializerSource.cs | 4 +- ...ingExpressionVisitor.ExpressionVisitors.cs | 2 +- .../EndToEndCosmosTest.cs | 44 ++-- .../CosmosModelBuilderGenericTest.cs | 29 +++ .../RelationalModelValidatorTest.cs | 4 +- .../Storage/RelationalTypeMapperTest.cs | 28 +-- .../Storage/RelationalTypeMapperTestBase.cs | 6 +- .../Storage/SqlServerTypeMappingSourceTest.cs | 209 +++++++++++++----- .../Storage/SqliteTypeMappingSourceTest.cs | 50 +++-- test/EFCore.Tests/CollectionComparerTest.cs | 12 +- .../Infrastructure/ModelValidatorTest.cs | 25 ++- .../ModelBuilding/ComplexTypeTestBase.cs | 2 +- 30 files changed, 396 insertions(+), 182 deletions(-) create mode 100644 src/EFCore/Metadata/Conventions/ElementMappingConvention.cs diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index c2717e60443..837b777070b 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -266,14 +266,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp { // Attempt to translate access into a primitive collection property (i.e. array column) - // TODO: We should be detecting primitive collections by looking at GetElementType() of the property and not at its type - // mapping; but #31469 is blocking that for shadow properties. if (_sqlTranslator.TryTranslatePropertyAccess(methodCallExpression, out var translatedExpression, out var property) - && property is IProperty regularProperty - && translatedExpression is SqlExpression - { - TypeMapping.ElementTypeMapping: RelationalTypeMapping - } sqlExpression) + && property is IProperty { IsPrimitiveCollection: true } regularProperty + && translatedExpression is SqlExpression sqlExpression) { var tableAlias = sqlExpression switch { @@ -2309,10 +2304,8 @@ static TableExpressionBase FindRootTableExpressionForColumn(ColumnExpression col } } - // TODO: Check that the property is a primitive collection property directly once we have that in metadata, rather than - // looking at the type mapping. var property = type.FindProperty(memberName); - if (property?.GetRelationalTypeMapping().ElementTypeMapping is null) + if (property?.IsPrimitiveCollection != true) { return null; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 84ce793fc1b..c5b89fc588f 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -2003,7 +2003,7 @@ protected override Expression VisitBinary(BinaryExpression node) if (node.Right is MethodCallExpression methodCallExpression && IsPropertyAssignment(methodCallExpression, out var property, out var parameter)) { - if (property!.GetTypeMapping().ElementTypeMapping != null + if (property!.IsPrimitiveCollection && !property.ClrType.IsArray) { var currentVariable = Variable(parameter!.Type); diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index 7e910650357..acc799a3f5c 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -167,10 +167,7 @@ protected override Expression VisitExtension(Expression extensionExpression) // which case we only have the CLR type (note that we cannot produce different SQLs based on the nullability of an *element* in // a parameter collection - our caching mechanism only supports varying by the nullability of the parameter itself (i.e. the // collection). - // TODO: if property is non-null, GetElementType() should never be null, but we have #31469 for shadow properties - var isElementNullable = property?.GetElementType() is null - ? elementClrType.IsNullableType() - : property.GetElementType()!.IsNullable; + var isElementNullable = property?.GetElementType()!.IsNullable; #pragma warning disable EF1001 // Internal EF Core API usage. var selectExpression = new SelectExpression( diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs index 49d230adbfc..9cef4d8619d 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs @@ -152,8 +152,7 @@ protected override void AppendUpdateColumnValue( stringBuilder.Append(columnModification.JsonPath); stringBuilder.Append("', "); - if (columnModification.Property != null - && columnModification.Property.GetTypeMapping().ElementTypeMapping == null) + if (columnModification.Property is { IsPrimitiveCollection: false }) { base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs index 807a17034b5..fae33b1871c 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -223,10 +223,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis // which case we only have the CLR type (note that we cannot produce different SQLs based on the nullability of an *element* in // a parameter collection - our caching mechanism only supports varying by the nullability of the parameter itself (i.e. the // collection). - // TODO: if property is non-null, GetElementType() should never be null, but we have #31469 for shadow properties - var isElementNullable = property?.GetElementType() is null - ? elementClrType.IsNullableType() - : property.GetElementType()!.IsNullable; + var isElementNullable = property?.GetElementType()!.IsNullable; #pragma warning disable EF1001 // Internal EF Core API usage. var selectExpression = new SelectExpression( diff --git a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs index d5a51861e72..626a9a0ce68 100644 --- a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs @@ -159,8 +159,7 @@ protected override void AppendUpdateColumnValue( stringBuilder.Append(columnModification.JsonPath); stringBuilder.Append("', "); - if (columnModification.Property != null - && columnModification.Property.GetTypeMapping().ElementTypeMapping == null) + if (columnModification.Property is { IsPrimitiveCollection: false }) { var providerClrType = (columnModification.Property.GetTypeMapping().Converter?.ProviderClrType ?? columnModification.Property.ClrType).UnwrapNullableType(); diff --git a/src/EFCore/ChangeTracking/ListComparer.cs b/src/EFCore/ChangeTracking/ListComparer.cs index d7543ac56bf..fa6c58e1309 100644 --- a/src/EFCore/ChangeTracking/ListComparer.cs +++ b/src/EFCore/ChangeTracking/ListComparer.cs @@ -92,7 +92,6 @@ public static bool Compare(IEnumerable? a, IEnumerable? b, V throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(ListComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); } @@ -129,7 +128,6 @@ public static IList Snapshot(IEnumerable source, ValueCompar throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(ListComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); } diff --git a/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs b/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs index c311f145d72..01df7706466 100644 --- a/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs +++ b/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs @@ -93,7 +93,6 @@ public static bool Compare(IEnumerable? a, IEnumerable? b, throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(NullableValueTypeListComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); } @@ -130,7 +129,6 @@ public static int GetHashCode(IEnumerable source, ValueComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); } diff --git a/src/EFCore/ChangeTracking/ObjectListComparer.cs b/src/EFCore/ChangeTracking/ObjectListComparer.cs index 3730c20acd0..bb721d35f4b 100644 --- a/src/EFCore/ChangeTracking/ObjectListComparer.cs +++ b/src/EFCore/ChangeTracking/ObjectListComparer.cs @@ -91,7 +91,6 @@ public static bool Compare(IEnumerable? a, IEnumerable? b, V throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(ListComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); } @@ -128,7 +127,6 @@ public static IList Snapshot(IEnumerable source, ValueCompar throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(ListComparer).ShortDisplayName(), typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); } diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 76eeb462189..36a139e12d8 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -60,6 +60,7 @@ public virtual void Validate(IModel model, IDiagnosticsLogger + /// Validates the mapping of primitive collection properties the model. + /// + /// The model to validate. + /// The logger to use. + protected virtual void ValidatePrimitiveCollections( + IModel model, + IDiagnosticsLogger logger) + { + foreach (var entityType in model.GetEntityTypes()) + { + Validate(entityType, logger); + } + + static void Validate(ITypeBase typeBase, IDiagnosticsLogger logger) + { + foreach (var property in typeBase.GetDeclaredProperties()) + { + var elementClrType = property.GetElementType()?.ClrType; + if (property is { IsPrimitiveCollection: true, ClrType.IsArray: false, ClrType.IsSealed: true } + && elementClrType is { IsSealed: true } + && elementClrType.TryGetElementType(typeof(IList<>)) == null) + { + throw new InvalidOperationException( + CoreStrings.BadListType( + property.ClrType.ShortDisplayName(), + typeof(IList<>).MakeGenericType(elementClrType).ShortDisplayName())); + } + } + + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) + { + Validate(complexProperty.ComplexType, logger); + } + } + } + /// /// Validates the mapping/configuration of query filters in the model. /// diff --git a/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs b/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs new file mode 100644 index 00000000000..d9b1f765868 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/ElementMappingConvention.cs @@ -0,0 +1,55 @@ +// 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.Metadata.Conventions; + +/// +/// A convention that ensures property mappings have any ElementMapping discovered by the type mapper. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public class ElementMappingConvention : IModelFinalizingConvention +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public ElementMappingConvention(ProviderConventionSetBuilderDependencies dependencies) + { + Dependencies = dependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + + /// + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + Validate(entityType); + } + + void Validate(IConventionTypeBase typeBase) + { + foreach (var property in typeBase.GetDeclaredProperties()) + { + var typeMapping = Dependencies.TypeMappingSource.FindMapping((IProperty)property); + if (typeMapping is { ElementTypeMapping: not null }) + { + property.SetElementType(property.ClrType.TryGetElementType(typeof(IEnumerable<>))); + } + } + + foreach (var complexProperty in typeBase.GetDeclaredComplexProperties()) + { + Validate(complexProperty.ComplexType); + } + } + } +} diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 4a356d038d8..43b3ec9adaa 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -100,6 +100,7 @@ public virtual ConventionSet CreateConventionSet() conventionSet.Add(new BackingFieldConvention(Dependencies)); conventionSet.Add(new QueryFilterRewritingConvention(Dependencies)); conventionSet.Add(new RuntimeModelConvention(Dependencies)); + conventionSet.Add(new ElementMappingConvention(Dependencies)); return conventionSet; } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index ca406251699..5c8b33830d0 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -62,7 +62,7 @@ protected virtual RuntimeModel Create(IModel model) var elementType = property.GetElementType(); if (elementType != null) { - var runtimeElementType = Create(runtimeProperty, elementType); + var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection); CreateAnnotations( elementType, runtimeElementType, static (convention, annotations, source, target, runtime) => convention.ProcessElementTypeAnnotations(annotations, source, target, runtime)); @@ -398,7 +398,7 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim property.GetJsonValueReaderWriter(), property.GetTypeMapping()); - private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element) + private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element, bool primitiveCollection) => runtimeProperty.SetElementType( element.ClrType, element.IsNullable, @@ -410,7 +410,8 @@ private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IEleme element.GetValueConverter(), element.GetValueComparer(), element.GetJsonValueReaderWriter(), - element.GetTypeMapping()); + element.GetTypeMapping(), + primitiveCollection); /// /// Updates the property annotations that will be set on the read-only object. @@ -524,7 +525,7 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeE var elementType = property.GetElementType(); if (elementType != null) { - var runtimeElementType = Create(runtimeProperty, elementType); + var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection); CreateAnnotations( elementType, runtimeElementType, static (convention, annotations, source, target, runtime) => convention.ProcessElementTypeAnnotations(annotations, source, target, runtime)); @@ -571,7 +572,7 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeC var elementType = property.GetElementType(); if (elementType != null) { - var runtimeElementType = Create(runtimeProperty, elementType); + var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection); CreateAnnotations( elementType, runtimeElementType, static (convention, annotations, source, target, runtime) => convention.ProcessElementTypeAnnotations(annotations, source, target, runtime)); diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index 93dc41b9d25..8ab768d6725 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -496,6 +496,7 @@ public virtual bool CanSetValueGeneratorFactory( { if (CanSetConversion(converter, configurationSource)) { + Metadata.SetElementType(null, configurationSource); Metadata.SetProviderClrType(null, configurationSource); Metadata.SetValueConverter(converter, configurationSource); @@ -531,6 +532,7 @@ public virtual bool CanSetConversion( { if (CanSetConversion(providerClrType, configurationSource)) { + Metadata.SetElementType(null, configurationSource); Metadata.SetValueConverter((ValueConverter?)null, configurationSource); Metadata.SetProviderClrType(providerClrType, configurationSource); diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 686c11091af..72d94ba293b 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -1220,7 +1220,8 @@ public virtual bool IsPrimitiveCollection { var elementType = GetElementType(); return elementType != null - && ClrType.TryGetElementType(typeof(IEnumerable<>))?.IsAssignableFrom(elementType!.ClrType) == true; + && ClrType.TryGetElementType(typeof(IEnumerable<>))?.UnwrapNullableType() + .IsAssignableFrom(elementType.ClrType.UnwrapNullableType()) == true; } } diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index e156b0f0455..2d3c3aa122a 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -125,6 +125,7 @@ public RuntimeProperty( /// The for this property. /// The for this property. /// The for this property. + /// A value indicating whether this property represents a primitive collection. /// The newly created property. public virtual RuntimeElementType SetElementType( Type clrType, @@ -137,7 +138,8 @@ public virtual RuntimeElementType SetElementType( ValueConverter? valueConverter = null, ValueComparer? valueComparer = null, JsonValueReaderWriter? jsonValueReaderWriter = null, - CoreTypeMapping? typeMapping = null) + CoreTypeMapping? typeMapping = null, + bool primitiveCollection = false) { var elementType = new RuntimeElementType( clrType, @@ -155,7 +157,7 @@ public virtual RuntimeElementType SetElementType( SetAnnotation(CoreAnnotationNames.ElementType, elementType); - IsPrimitiveCollection = ClrType.TryGetElementType(typeof(IEnumerable<>))?.IsAssignableFrom(clrType) == true; + IsPrimitiveCollection = primitiveCollection; return elementType; } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 20ca189003f..be1e60cf391 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -203,12 +203,12 @@ public static string BadJsonValueReaderWriterType(object? givenType) givenType); /// - /// The type '{givenType}' cannot be used with '{comparerType}' because it does not implement '{listType}'. Collections of primitive types must be ordered lists. + /// The type '{givenType}' cannot be used as a primitive collection because it is not an array and does not implement '{listType}'. Collections of primitive types must be arrays or ordered lists. /// - public static string BadListType(object? givenType, object? comparerType, object? listType) + public static string BadListType(object? givenType, object? listType) => string.Format( - GetString("BadListType", nameof(givenType), nameof(comparerType), nameof(listType)), - givenType, comparerType, listType); + GetString("BadListType", nameof(givenType), nameof(listType)), + givenType, listType); /// /// The type '{givenType}' cannot be used as a value comparer because it does not inherit from '{expectedType}'. Make sure to inherit value comparers from '{expectedType}'. diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 024c0369942..4b08beee106 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -184,7 +184,7 @@ The type '{givenType}' cannot be used as a 'JsonValueReaderWriter' because it does not inherit from the generic 'JsonValueReaderWriter<TValue>'. Make sure to inherit json reader/writers from 'JsonValueReaderWriter<TValue>'. - The type '{givenType}' cannot be used with '{comparerType}' because it does not implement '{listType}'. Collections of primitive types must be ordered lists. + The type '{givenType}' cannot be used as a primitive collection because it is not an array and does not implement '{listType}'. Collections of primitive types must be arrays or ordered lists. The type '{givenType}' cannot be used as a value comparer because it does not inherit from '{expectedType}'. Make sure to inherit value comparers from '{expectedType}'. diff --git a/src/EFCore/Query/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs index 894186f1df0..138986831a5 100644 --- a/src/EFCore/Query/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Query/Internal/EntityMaterializerSource.cs @@ -163,9 +163,7 @@ IComplexProperty complexProperty static Expression CreateMemberAssignment(Expression parameter, MemberInfo memberInfo, IPropertyBase property, Expression value) { - if (property is IProperty prop - && prop.GetTypeMapping().ElementTypeMapping != null - && !prop.ClrType.IsArray) + if (property is IProperty { IsPrimitiveCollection: true, ClrType.IsArray: false }) { var currentVariable = Expression.Variable(property.ClrType); return Expression.Block( diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index 285c4dd5bf1..26388a01c89 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -129,7 +129,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp : memberIdentity.Name is not null ? entityType.FindProperty(memberIdentity.Name) : null; - if (property?.GetTypeMapping().ElementTypeMapping != null) + if (property?.IsPrimitiveCollection == true) { return new PrimitiveCollectionReference(root, property); } diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index f4178ce5f16..3f26733c3b2 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -668,31 +668,27 @@ await Can_add_update_delete_with_collection>( "1" }); - await Assert.ThrowsAsync( // #31616 - async () => + // See #25343 + await Can_add_update_delete_with_collection( + new List { - // See #25343 - await Can_add_update_delete_with_collection( - new List - { - EntityType.Base, - EntityType.Derived, - EntityType.Derived - }, - c => - { - c.Collection.Clear(); - c.Collection.Add(EntityType.Base); - }, - new List { EntityType.Base }, - modelBuilder => modelBuilder.Entity>>( - c => - c.Property(s => s.Collection) - .HasConversion( - m => m.Select(v => (int)v).ToList(), p => p.Select(v => (EntityType)v).ToList(), - new ListComparer>( - ValueComparer.CreateDefault(typeof(EntityType), false), readOnly: false)))); - }); + EntityType.Base, + EntityType.Derived, + EntityType.Derived + }, + c => + { + c.Collection.Clear(); + c.Collection.Add(EntityType.Base); + }, + new List { EntityType.Base }, + modelBuilder => modelBuilder.Entity>>( + c => + c.Property(s => s.Collection) + .HasConversion( + m => m.Select(v => (int)v).ToList(), p => p.Select(v => (EntityType)v).ToList(), + new ListComparer>( + ValueComparer.CreateDefault(typeof(EntityType), false), readOnly: false)))); await Can_add_update_delete_with_collection( new[] { 1f, 2 }, diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index 4b04a27d661..1f32b625230 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -333,6 +333,35 @@ protected override TestModelBuilder CreateModelBuilder(Action() + .Entity() + .ComplexProperty(e => e.Customer) + .HasTypeAnnotation("foo", "bar") + .HasPropertyAnnotation("foo2", "bar2") + .Ignore(c => c.Details) + .Ignore(c => c.Orders); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + + Assert.Equal("bar", complexProperty.ComplexType["foo"]); + Assert.Equal("bar2", complexProperty["foo2"]); + Assert.Equal(typeof(Customer).Name, complexProperty.Name); + Assert.Equal( + @"Customer (Customer) Required + ComplexType: ComplexProperties.Customer#Customer + Properties: " + @" + AlternateKey (Guid) Required + Id (int) Required + Name (string) + Notes (List)", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true); + } + public override void Properties_can_have_provider_type_set_for_type() { var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 6efa346ff7f..97377dab774 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -726,9 +726,7 @@ public virtual void Detects_shared_table_root_cycle() modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); modelBuilder.Entity().ToTable("Table"); - VerifyError( - CoreStrings.IdentifyingRelationshipCycle("A -> B"), - modelBuilder); + VerifyError(CoreStrings.RelationshipCycle("B", "AId", "ValueConverter"), modelBuilder); } [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs index 1b2310f06b3..387f89697ac 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTest.cs @@ -103,7 +103,7 @@ public void Does_type_mapping_from_named_binary_with_no_MaxLength() public void Key_with_store_type_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "money", @@ -118,7 +118,7 @@ public void Key_with_store_type_is_picked_up_by_FK() public void Does_default_type_mapping_from_decimal() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "default_decimal_mapping", @@ -129,7 +129,7 @@ public void Does_default_type_mapping_from_decimal() public void Does_type_mapping_from_decimal_with_precision_only() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "decimal_mapping(16)", @@ -140,7 +140,7 @@ public void Does_type_mapping_from_decimal_with_precision_only() public void Does_type_mapping_from_decimal_with_precision_and_scale() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "decimal_mapping(18,7)", @@ -237,22 +237,22 @@ public void StoreTypeNameBase_is_trimmed() Assert.False(mapping.IsFixedLength); } - protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource() + protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource(IModel model) => new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); public RelationalTypeMapping GetMapping(Type type) - => CreateRelationalTypeMappingSource().FindMapping(type); + => CreateRelationalTypeMappingSource(CreateModel()).FindMapping(type); public RelationalTypeMapping GetMapping(IProperty property) - => CreateRelationalTypeMappingSource().FindMapping(property); + => CreateRelationalTypeMappingSource(CreateModel()).FindMapping(property); [ConditionalFact] public void String_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "just_string_fixed(200)", @@ -267,7 +267,7 @@ public void String_key_with_max_fixed_length_is_picked_up_by_FK() public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "just_binary_fixed(100)", @@ -282,7 +282,7 @@ public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() public void String_key_with_unicode_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "ansi_string(900)", @@ -297,7 +297,7 @@ public void String_key_with_unicode_is_picked_up_by_FK() public void Key_store_type_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "money", @@ -312,7 +312,7 @@ public void Key_store_type_is_preferred_if_specified() public void String_FK_max_length_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "just_string_fixed(200)", @@ -327,7 +327,7 @@ public void String_FK_max_length_is_preferred_if_specified() public void Binary_FK_max_length_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "just_binary_fixed(100)", @@ -342,7 +342,7 @@ public void Binary_FK_max_length_is_preferred_if_specified() public void String_FK_unicode_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "ansi_string(900)", diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs index d80acf7cc44..50021f8ba0e 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs @@ -85,7 +85,7 @@ protected RelationalTypeMapping GetTypeMapping( } }).FinalizeModel(); - return CreateRelationalTypeMappingSource().GetMapping(propertyType, model); + return CreateRelationalTypeMappingSource(model).GetMapping(propertyType, model); } else { @@ -135,12 +135,12 @@ protected RelationalTypeMapping GetTypeMapping( } var model = modelBuilder.Model.FinalizeModel(); - return CreateRelationalTypeMappingSource().GetMapping(model.FindEntityType(typeof(MyType)).FindProperty(property.Name)); + return CreateRelationalTypeMappingSource(model).GetMapping(model.FindEntityType(typeof(MyType)).FindProperty(property.Name)); } } protected abstract ModelBuilder CreateModelBuilder(Action configureConventions = null); - protected abstract IRelationalTypeMappingSource CreateRelationalTypeMappingSource(); + protected abstract IRelationalTypeMappingSource CreateRelationalTypeMappingSource(IModel model); protected class MyType { diff --git a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs index 39136084ef7..6d235e9ea46 100644 --- a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs @@ -313,7 +313,10 @@ public void Does_key_SQL_Server_string_mapping(Type type, bool? unicode, bool? f property.SetIsFixedLength(fixedLength); ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.String, typeMapping.DbType); Assert.Equal("nvarchar(450)", typeMapping.StoreType); @@ -351,7 +354,10 @@ public void Does_foreign_key_SQL_Server_string_mapping(Type type, bool? unicode, var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.String, typeMapping.DbType); Assert.Equal("nvarchar(450)", typeMapping.StoreType); @@ -390,7 +396,10 @@ public void Does_required_foreign_key_SQL_Server_string_mapping(Type type, bool? ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.String, typeMapping.DbType); Assert.Equal("nvarchar(450)", typeMapping.StoreType); @@ -426,7 +435,10 @@ public void Does_indexed_column_SQL_Server_string_mapping(Type type, bool? unico property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.String, typeMapping.DbType); Assert.Equal("nvarchar(450)", typeMapping.StoreType); @@ -456,9 +468,11 @@ public void Does_IndexAttribute_column_SQL_Server_string_mapping(bool? unicode, var property = entityType.FindProperty("Name"); property.SetIsUnicode(unicode); property.SetIsFixedLength(fixedLength); - entityType.Model.FinalizeModel(); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyTypeWithIndexAttribute))!.FindProperty("Name")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.String, typeMapping.DbType); Assert.Equal("nvarchar(450)", typeMapping.StoreType); @@ -481,9 +495,7 @@ public void Does_IndexAttribute_column_SQL_Server_primitive_collection_mapping(b property.SetIsFixedLength(fixedLength); var model = entityType.Model.FinalizeModel(); - var typeMappingSource = CreateRelationalTypeMappingSource(); - model.ModelDependencies = new RuntimeModelDependencies(typeMappingSource, null!, null!); - + var typeMappingSource = CreateRelationalTypeMappingSource(model); var runtimeProperty = model.FindEntityType(typeof(MyTypeWithIndexAttributeOnCollection))!.FindProperty("Ints")!; var typeMapping = typeMappingSource.GetMapping(runtimeProperty); @@ -718,7 +730,10 @@ public void Does_key_SQL_Server_string_mapping_ansi(Type type, bool? fixedLength property.SetIsFixedLength(fixedLength); ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(900)", typeMapping.StoreType); @@ -752,7 +767,10 @@ public void Does_foreign_key_SQL_Server_string_mapping_ansi(Type type, bool? fix var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(900)", typeMapping.StoreType); @@ -787,7 +805,10 @@ public void Does_required_foreign_key_SQL_Server_string_mapping_ansi(Type type, ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(900)", typeMapping.StoreType); @@ -819,7 +840,10 @@ public void Does_indexed_column_SQL_Server_string_mapping_ansi(Type type, bool? property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(900)", typeMapping.StoreType); @@ -847,9 +871,11 @@ public void Does_IndexAttribute_column_SQL_Server_string_mapping_ansi(bool? fixe var property = entityType.FindProperty("Name"); property.SetIsUnicode(false); property.SetIsFixedLength(fixedLength); - entityType.Model.FinalizeModel(); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyTypeWithIndexAttribute))!.FindProperty("Name")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.AnsiString, typeMapping.DbType); Assert.Equal("varchar(900)", typeMapping.StoreType); @@ -870,9 +896,7 @@ public void Does_IndexAttribute_column_SQL_Server_primitive_collection_mapping_a property.SetIsFixedLength(fixedLength); var model = entityType.Model.FinalizeModel(); - var typeMappingSource = CreateRelationalTypeMappingSource(); - model.ModelDependencies = new RuntimeModelDependencies(typeMappingSource, null!, null!); - + var typeMappingSource = CreateRelationalTypeMappingSource(model); var runtimeProperty = model.FindEntityType(typeof(MyTypeWithIndexAttributeOnCollection))!.FindProperty("Ints")!; var typeMapping = typeMappingSource.GetMapping(runtimeProperty); @@ -1052,7 +1076,10 @@ private RelationalTypeMapping CreateBinaryMapping(string typeName, int? maxLengt property.SetMaxLength(maxLength); } - return CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyBinaryProp")!; + return typeMappingSource.GetMapping(runtimeProperty); } [ConditionalTheory] @@ -1065,7 +1092,10 @@ public void Does_key_SQL_Server_binary_mapping(bool? fixedLength) property.SetIsFixedLength(fixedLength); ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("varbinary(900)", typeMapping.StoreType); @@ -1085,7 +1115,10 @@ public void Does_foreign_key_SQL_Server_binary_mapping(bool? fixedLength) var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); @@ -1106,7 +1139,10 @@ public void Does_required_foreign_key_SQL_Server_binary_mapping(bool? fixedLengt ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); @@ -1124,7 +1160,10 @@ public void Does_indexed_column_SQL_Server_binary_mapping(bool? fixedLength) property.SetIsFixedLength(fixedLength); entityType.AddIndex(property); - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.False(typeMapping.IsFixedLength); Assert.Equal(DbType.Binary, typeMapping.DbType); @@ -1139,7 +1178,10 @@ public void Does_non_key_SQL_Server_rowversion_mapping() property.IsConcurrencyToken = true; property.ValueGenerated = ValueGenerated.OnAddOrUpdate; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("rowversion", typeMapping.StoreType); @@ -1156,7 +1198,10 @@ public void Does_non_key_SQL_Server_required_rowversion_mapping() property.ValueGenerated = ValueGenerated.OnAddOrUpdate; property.IsNullable = false; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.Equal("rowversion", typeMapping.StoreType); @@ -1171,7 +1216,10 @@ public void Does_not_do_rowversion_mapping_for_non_computed_concurrency_tokens() var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsConcurrencyToken = true; - var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(MyType))!.FindProperty("MyProp")!; + var typeMapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Equal(DbType.Binary, typeMapping.DbType); Assert.False(typeMapping.IsFixedLength); @@ -1181,32 +1229,36 @@ public void Does_not_do_rowversion_mapping_for_non_computed_concurrency_tokens() [ConditionalFact] public void Does_default_mappings_for_sequence_types() { - Assert.Equal("int", CreateRelationalTypeMappingSource().GetMapping(typeof(int)).StoreType); - Assert.Equal("smallint", CreateRelationalTypeMappingSource().GetMapping(typeof(short)).StoreType); - Assert.Equal("bigint", CreateRelationalTypeMappingSource().GetMapping(typeof(long)).StoreType); - Assert.Equal("tinyint", CreateRelationalTypeMappingSource().GetMapping(typeof(byte)).StoreType); + var model = CreateModel(); + Assert.Equal("int", CreateRelationalTypeMappingSource(model).GetMapping(typeof(int)).StoreType); + Assert.Equal("smallint", CreateRelationalTypeMappingSource(model).GetMapping(typeof(short)).StoreType); + Assert.Equal("bigint", CreateRelationalTypeMappingSource(model).GetMapping(typeof(long)).StoreType); + Assert.Equal("tinyint", CreateRelationalTypeMappingSource(model).GetMapping(typeof(byte)).StoreType); } [ConditionalFact] public void Does_default_mappings_for_strings_and_byte_arrays() { - Assert.Equal("nvarchar(max)", CreateRelationalTypeMappingSource().GetMapping(typeof(string)).StoreType); - Assert.Equal("varbinary(max)", CreateRelationalTypeMappingSource().GetMapping(typeof(byte[])).StoreType); + var model = CreateModel(); + Assert.Equal("nvarchar(max)", CreateRelationalTypeMappingSource(model).GetMapping(typeof(string)).StoreType); + Assert.Equal("varbinary(max)", CreateRelationalTypeMappingSource(model).GetMapping(typeof(byte[])).StoreType); } [ConditionalFact] public void Does_default_mappings_for_values() { - Assert.Equal("nvarchar(max)", CreateRelationalTypeMappingSource().GetMappingForValue("Cheese").StoreType); - Assert.Equal("varbinary(max)", CreateRelationalTypeMappingSource().GetMappingForValue(new byte[1]).StoreType); - Assert.Equal("datetime2", CreateRelationalTypeMappingSource().GetMappingForValue(new DateTime()).StoreType); + var model = CreateModel(); + Assert.Equal("nvarchar(max)", CreateRelationalTypeMappingSource(model).GetMappingForValue("Cheese").StoreType); + Assert.Equal("varbinary(max)", CreateRelationalTypeMappingSource(model).GetMappingForValue(new byte[1]).StoreType); + Assert.Equal("datetime2", CreateRelationalTypeMappingSource(model).GetMappingForValue(new DateTime()).StoreType); } [ConditionalFact] public void Does_default_mappings_for_null_values() { - Assert.Equal("NULL", CreateRelationalTypeMappingSource().GetMappingForValue(null).StoreType); - Assert.Equal("NULL", CreateRelationalTypeMappingSource().GetMappingForValue(DBNull.Value).StoreType); + var model = CreateModel(); + Assert.Equal("NULL", CreateRelationalTypeMappingSource(model).GetMappingForValue(null).StoreType); + Assert.Equal("NULL", CreateRelationalTypeMappingSource(model).GetMappingForValue(DBNull.Value).StoreType); } [ConditionalFact] @@ -1214,17 +1266,19 @@ public void Throws_for_unrecognized_property_types() { var property = ((IMutableModel)new Model()).AddEntityType("Entity1") .AddProperty("Strange", typeof(object)); - var ex = Assert.Throws(() => CreateRelationalTypeMappingSource().GetMapping((IProperty)property)); + var model = CreateModel(); + + var ex = Assert.Throws(() => CreateRelationalTypeMappingSource(model).GetMapping((IProperty)property)); Assert.Equal( RelationalStrings.UnsupportedPropertyType("Entity1 (Dictionary)", "Strange", "object"), ex.Message); Assert.Equal( RelationalStrings.UnsupportedType("object"), - Assert.Throws(() => CreateRelationalTypeMappingSource().GetMapping(typeof(object))).Message); + Assert.Throws(() => CreateRelationalTypeMappingSource(model).GetMapping(typeof(object))).Message); Assert.Equal( RelationalStrings.UnsupportedStoreType("object"), - Assert.Throws(() => CreateRelationalTypeMappingSource().GetMapping("object")).Message); + Assert.Throws(() => CreateRelationalTypeMappingSource(model).GetMapping("object")).Message); } [ConditionalTheory] @@ -1278,7 +1332,7 @@ public void Throws_for_unrecognized_property_types() [InlineData("VARCHAR(max)", typeof(string), -1, false, false, "VARCHAR(max)")] public void Can_map_by_store_type(string storeType, Type type, int? size, bool unicode, bool fixedLength, string expectedType = null) { - var mapping = CreateRelationalTypeMappingSource().FindMapping(storeType); + var mapping = CreateRelationalTypeMappingSource(CreateModel()).FindMapping(storeType); Assert.Same(type, mapping.ClrType); Assert.Equal(size, mapping.Size); @@ -1295,7 +1349,7 @@ public void Can_map_by_store_type(string storeType, Type type, int? size, bool u [InlineData(typeof(TimeSpan), "time")] public void Can_map_by_clr_and_store_types(Type clrType, string storeType) { - var mapping = CreateRelationalTypeMappingSource().FindMapping(clrType, storeType); + var mapping = CreateRelationalTypeMappingSource(CreateModel()).FindMapping(clrType, storeType); Assert.Equal(storeType, mapping.StoreType); Assert.Same(clrType, mapping.ClrType); @@ -1324,7 +1378,10 @@ public void Can_map_string_base_type_name_and_size(string typeName) .HasMaxLength(2018) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(StringCheese))!.FindProperty("StringWithSize")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(string), mapping.ClrType); Assert.Equal(2018, mapping.Size); @@ -1356,7 +1413,10 @@ public void Can_map_collection_base_type_name_and_size(string typeName) .HasMaxLength(2018) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(StringCheese))!.FindProperty("CollectionWithSize")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(IEnumerable), mapping.ClrType); Assert.Equal(2018, mapping.Size); @@ -1384,7 +1444,10 @@ public void Can_map_datetime_base_type_columnType_with_precision(string typeName .HasColumnType(typeName) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("DateTimeWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(DateTime), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1410,7 +1473,10 @@ public void Can_map_datetime_base_type_precision(string typeName, int precision) .HasPrecision(precision) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("DateTimeWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(DateTime), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1437,7 +1503,10 @@ public void Can_map_datetimeoffset_base_type_columnType_with_precision(string ty .HasColumnType(typeName) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("DateTimeOffsetWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(DateTimeOffset), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1463,7 +1532,10 @@ public void Can_map_datetimeoffset_base_type_precision(string typeName, int prec .HasPrecision(precision) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("DateTimeOffsetWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(DateTimeOffset), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1490,7 +1562,10 @@ public void Can_map_time_base_type_columnType_with_precision(string typeName, in .HasColumnType(typeName) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("TimeSpanWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(TimeSpan), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1516,7 +1591,10 @@ public void Can_map_time_base_type_precision(string typeName, int precision) .HasPrecision(precision) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(VarTimeEntity))!.FindProperty("TimeSpanWithPrecision")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(TimeSpan), mapping.ClrType); Assert.Equal(precision, mapping.Precision); @@ -1546,7 +1624,10 @@ public void Can_map_binary_base_type_name_and_size(string typeName) .HasMaxLength(2018) .Metadata; - var mapping = CreateRelationalTypeMappingSource().FindMapping((IProperty)property); + var model = property.DeclaringType.Model.FinalizeModel(); + var typeMappingSource = CreateRelationalTypeMappingSource(model); + var runtimeProperty = model.FindEntityType(typeof(StringCheese))!.FindProperty("BinaryWithSize")!; + var mapping = typeMappingSource.GetMapping(runtimeProperty); Assert.Same(typeof(byte[]), mapping.ClrType); Assert.Equal(2018, mapping.Size); @@ -1566,7 +1647,7 @@ private class StringCheese public void Key_with_store_type_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "money", @@ -1581,7 +1662,7 @@ public void Key_with_store_type_is_picked_up_by_FK() public void String_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "nchar(200)", @@ -1596,7 +1677,7 @@ public void String_key_with_max_fixed_length_is_picked_up_by_FK() public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "binary(100)", @@ -1611,7 +1692,7 @@ public void Binary_key_with_max_fixed_length_is_picked_up_by_FK() public void String_key_with_unicode_is_picked_up_by_FK() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "varchar(900)", @@ -1626,7 +1707,7 @@ public void String_key_with_unicode_is_picked_up_by_FK() public void Key_store_type_if_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "money", @@ -1641,7 +1722,7 @@ public void Key_store_type_if_preferred_if_specified() public void String_FK_max_length_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "nchar(200)", @@ -1656,7 +1737,7 @@ public void String_FK_max_length_is_preferred_if_specified() public void Binary_FK_max_length_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "binary(100)", @@ -1671,7 +1752,7 @@ public void Binary_FK_max_length_is_preferred_if_specified() public void String_FK_unicode_is_preferred_if_specified() { var model = CreateModel(); - var mapper = CreateRelationalTypeMappingSource(); + var mapper = CreateRelationalTypeMappingSource(model); Assert.Equal( "varchar(900)", @@ -1701,11 +1782,17 @@ public RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInf => new StringTypeMapping("datetime2", null); } - protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource() - => new SqlServerTypeMappingSource( + protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource(IModel model) + { + var typeMappingSource = new SqlServerTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); + model.ModelDependencies = new RuntimeModelDependencies(typeMappingSource, null!, null!); + + return typeMappingSource; + } + private enum LongEnum : long { } diff --git a/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingSourceTest.cs b/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingSourceTest.cs index 9ad8c022c16..c89e2e42b53 100644 --- a/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingSourceTest.cs +++ b/test/EFCore.Sqlite.Tests/Storage/SqliteTypeMappingSourceTest.cs @@ -117,7 +117,7 @@ public void Does_mappings_for_store_type(string storeType, Type clrType, DbType? { foreach (var type in new[] { storeType, storeType.ToLower(), storeType.Substring(0, 1) + storeType.Substring(1).ToLower() }) { - var mapping = CreateRelationalTypeMappingSource().FindMapping(type)!; + var mapping = CreateRelationalTypeMappingSource(CreateModel()).FindMapping(type)!; Assert.Equal(storeType.ToLower(), mapping.StoreType.ToLower()); Assert.Equal(Nullable.GetUnderlyingType(clrType) ?? clrType, mapping.ClrType); Assert.Equal(dbType, mapping.DbType); @@ -289,27 +289,29 @@ public void Does_mappings_for_both_store_and_CLR_type(string storeType, Type clr [ConditionalFact] public void Does_default_mappings_for_values() { - Assert.Equal("TEXT", CreateRelationalTypeMappingSource().GetMappingForValue("Cheese").StoreType); - Assert.Equal("BLOB", CreateRelationalTypeMappingSource().GetMappingForValue(new byte[1]).StoreType); - Assert.Equal("TEXT", CreateRelationalTypeMappingSource().GetMappingForValue(new DateTime()).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue(1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue(1L).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((byte)1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((short)1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((uint)1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((ulong)1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((sbyte)1).StoreType); - Assert.Equal("INTEGER", CreateRelationalTypeMappingSource().GetMappingForValue((ushort)1).StoreType); - Assert.Equal("TEXT", CreateRelationalTypeMappingSource().GetMappingForValue(1.0m).StoreType); - Assert.Equal("REAL", CreateRelationalTypeMappingSource().GetMappingForValue(1.0).StoreType); - Assert.Equal("REAL", CreateRelationalTypeMappingSource().GetMappingForValue(1.0f).StoreType); + var model = CreateModel(); + Assert.Equal("TEXT", CreateRelationalTypeMappingSource(model).GetMappingForValue("Cheese").StoreType); + Assert.Equal("BLOB", CreateRelationalTypeMappingSource(model).GetMappingForValue(new byte[1]).StoreType); + Assert.Equal("TEXT", CreateRelationalTypeMappingSource(model).GetMappingForValue(new DateTime()).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue(1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue(1L).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((byte)1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((short)1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((uint)1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((ulong)1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((sbyte)1).StoreType); + Assert.Equal("INTEGER", CreateRelationalTypeMappingSource(model).GetMappingForValue((ushort)1).StoreType); + Assert.Equal("TEXT", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0m).StoreType); + Assert.Equal("REAL", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0).StoreType); + Assert.Equal("REAL", CreateRelationalTypeMappingSource(model).GetMappingForValue(1.0f).StoreType); } [ConditionalFact] public void Does_default_mappings_for_null_values() { - Assert.Equal("NULL", CreateRelationalTypeMappingSource().GetMappingForValue(null).StoreType); - Assert.Equal("NULL", CreateRelationalTypeMappingSource().GetMappingForValue(DBNull.Value).StoreType); + var model = CreateModel(); + Assert.Equal("NULL", CreateRelationalTypeMappingSource(model).GetMappingForValue(null).StoreType); + Assert.Equal("NULL", CreateRelationalTypeMappingSource(model).GetMappingForValue(DBNull.Value).StoreType); } [ConditionalFact] @@ -317,13 +319,13 @@ public void Throws_for_unrecognized_property_types() { var property = ((IMutableModel)new Model()).AddEntityType("Entity1") .AddProperty("Strange", typeof(object)); - var ex = Assert.Throws(() => CreateRelationalTypeMappingSource().GetMapping((IProperty)property)); + var ex = Assert.Throws(() => CreateRelationalTypeMappingSource(CreateModel()).GetMapping((IProperty)property)); Assert.Equal( RelationalStrings.UnsupportedPropertyType("Entity1 (Dictionary)", "Strange", "object"), ex.Message); Assert.Equal( RelationalStrings.UnsupportedType("object"), - Assert.Throws(() => CreateRelationalTypeMappingSource().GetMapping(typeof(object))).Message); + Assert.Throws(() => CreateRelationalTypeMappingSource(CreateModel()).GetMapping(typeof(object))).Message); } [ConditionalFact] @@ -345,11 +347,17 @@ public RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInf => new StringTypeMapping("datetime2", null); } - protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource() - => new SqliteTypeMappingSource( + protected override IRelationalTypeMappingSource CreateRelationalTypeMappingSource(IModel model) + { + var typeMappingSource = new SqliteTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); + model.ModelDependencies = new RuntimeModelDependencies(typeMappingSource, null!, null!); + + return typeMappingSource; + } + private enum LongEnum : long { } diff --git a/test/EFCore.Tests/CollectionComparerTest.cs b/test/EFCore.Tests/CollectionComparerTest.cs index 82afb3463ad..9e83db642ca 100644 --- a/test/EFCore.Tests/CollectionComparerTest.cs +++ b/test/EFCore.Tests/CollectionComparerTest.cs @@ -504,15 +504,15 @@ public void List_comparer_throws_when_used_with_non_list() var comparer = new ListComparer(new ValueComparer(favorStructuralComparisons: false)); Assert.Equal( - CoreStrings.BadListType("HashSet", "ListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Equals(new List(), new HashSet())).Message); Assert.Equal( - CoreStrings.BadListType("HashSet", "ListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Equals(new HashSet(), new List())).Message); Assert.Equal( - CoreStrings.BadListType("HashSet", "ListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Snapshot(new HashSet())).Message); } @@ -522,15 +522,15 @@ public void Nullable_list_comparer_throws_when_used_with_non_list() var comparer = new NullableValueTypeListComparer(new ValueComparer(favorStructuralComparisons: false)); Assert.Equal( - CoreStrings.BadListType("HashSet", "NullableValueTypeListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Equals(new List(), new HashSet())).Message); Assert.Equal( - CoreStrings.BadListType("HashSet", "NullableValueTypeListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Equals(new HashSet(), new List())).Message); Assert.Equal( - CoreStrings.BadListType("HashSet", "NullableValueTypeListComparer", "IList"), + CoreStrings.BadListType("HashSet", "IList"), Assert.Throws(() => comparer.Snapshot(new HashSet())).Message); } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 2d214c2c1c6..6d31ba320db 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -183,6 +183,27 @@ protected class WithCollectionConversion public string[] SomeStrings { get; set; } } + [ConditionalFact] + public virtual void Throws_when_mapping_concrete_sealed_type_that_does_not_implement_IList() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity( + eb => + { + eb.Property(e => e.Id); + eb.PrimitiveCollection(e => e.SomeString); + }); + + VerifyError(CoreStrings.BadListType("string", "IList"), modelBuilder, sensitiveDataLoggingEnabled: false); + } + + protected class WithStringCollection + { + public int Id { get; set; } + public string SomeString { get; set; } + } + [ConditionalFact] public virtual void Ignores_binary_keys_and_strings_without_custom_comparer() { @@ -482,9 +503,7 @@ public virtual void Detects_identifying_relationship_cycle() modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - VerifyError( - CoreStrings.IdentifyingRelationshipCycle("A -> B -> C"), - modelBuilder); + VerifyError(CoreStrings.RelationshipCycle("B", "AId", "ValueConverter"), modelBuilder); } [ConditionalFact] diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs index caaf6931743..9264db0504f 100644 --- a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -39,7 +39,7 @@ public virtual void Can_set_complex_property_annotation() AlternateKey (Guid) Required Id (int) Required Name (string) - Notes (List)", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true); + Notes (List) Element type: string", complexProperty.ToDebugString(), ignoreLineEndingDifferences: true); } [ConditionalFact]