diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 5e2464e3b..749e3d3a0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -142,10 +142,21 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType #pragma warning restore EF1001 var elementJsonReaderWriter = elementMapping.JsonValueReaderWriter; - if (elementJsonReaderWriter is not null && !typeof(TElement).UnwrapNullableType().IsAssignableTo(elementJsonReaderWriter.ValueType)) + + if (elementJsonReaderWriter is not null) { - throw new InvalidOperationException( - $"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementJsonReaderWriter.ValueType.Name}' instead of '{typeof(TElement).UnwrapNullableType()}', the JsonValueReaderWriter is '{elementJsonReaderWriter.GetType().Name}')."); + if (!typeof(TElement).UnwrapNullableType().IsAssignableTo(elementJsonReaderWriter.ValueType)) + { + throw new InvalidOperationException( + $"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementJsonReaderWriter.ValueType.Name}' instead of '{typeof(TElement).UnwrapNullableType()}', the JsonValueReaderWriter is '{elementJsonReaderWriter.GetType().Name}')."); + } + + if (elementJsonReaderWriter.ValueType.IsNullableValueType() + || !elementJsonReaderWriter.ValueType.IsAssignableFrom(elementType.UnwrapNullableType())) + { + elementJsonReaderWriter = (JsonValueReaderWriter)Activator.CreateInstance( + typeof(JsonCastValueReaderWriter<>).MakeGenericType(elementType.UnwrapNullableType()), elementJsonReaderWriter)!; + } } // If there's no JsonValueReaderWriter on the element, we also don't set one on its array (this is for rare edge cases such as diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 4613bd4fa..fce10db4e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -385,7 +385,7 @@ WHERE GREATEST(30, p."NullableInt", NULL) = 30 """); } - public override async Task Inline_collection_with_single_parameter_element_Contains() + public override async Task Inline_collection_with_single_parameter_element_Contains() { await base.Inline_collection_with_single_parameter_element_Contains(); @@ -1438,7 +1438,7 @@ public override async Task Column_collection_ElementAt() """); } - public override async Task Column_collection_First() + public override async Task Column_collection_First() { await base.Column_collection_First(); @@ -1534,7 +1534,7 @@ public override async Task Column_collection_Skip_Take() """); } - public override async Task Column_collection_Where_Skip() + public override async Task Column_collection_Where_Skip() { await base.Column_collection_Where_Skip(); @@ -2486,7 +2486,7 @@ WHERE cardinality(array_remove(p."Ints", 1)) = 1 """); } - public override async Task Parameter_collection_of_structs_Contains_nullable_struct() + public override async Task Parameter_collection_of_structs_Contains_nullable_struct() { await base.Parameter_collection_of_structs_Contains_nullable_struct(); @@ -2562,9 +2562,29 @@ WHERE NOT (p."NullableWrappedId" = ANY (@values) AND p."NullableWrappedId" = ANY """); } - public override Task Parameter_collection_of_nullable_structs_Contains_nullable_struct_with_nullable_comparer() - => Assert.ThrowsAnyAsync( - () => base.Parameter_collection_of_nullable_structs_Contains_nullable_struct_with_nullable_comparer()); + public override async Task Parameter_collection_of_nullable_structs_Contains_nullable_struct_with_nullable_comparer() + { + await base.Parameter_collection_of_nullable_structs_Contains_nullable_struct_with_nullable_comparer(); + + AssertSql( +""" +@values={ NULL +'22' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."NullableWrappedId", p."NullableWrappedIdWithNullableComparer", p."String", p."Strings", p."WrappedId" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."NullableWrappedIdWithNullableComparer" = ANY (@values) OR (p."NullableWrappedIdWithNullableComparer" IS NULL AND array_position(@values, NULL) IS NOT NULL) +""", + // + """ +@values={ '11' +'44' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."NullableWrappedId", p."NullableWrappedIdWithNullableComparer", p."String", p."Strings", p."WrappedId" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."NullableWrappedIdWithNullableComparer" = ANY (@values) AND p."NullableWrappedIdWithNullableComparer" = ANY (@values) IS NOT NULL) AND (p."NullableWrappedIdWithNullableComparer" IS NOT NULL OR array_position(@values, NULL) IS NULL) +"""); + } public override async Task Inline_collection_Contains_with_IEnumerable_EF_Parameter()