diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 9ed58f72694..45c4d5197f6 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -651,8 +651,15 @@ protected virtual Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpress /// The for which to generate SQL. protected virtual Expression VisitSqlConstant(SqlConstantExpression sqlConstantExpression) { + if (sqlConstantExpression.TypeMapping is null) + { + throw new UnreachableException( + "SqlConstantExpression has no type mapping. " + + "Please file a bug report at https://github.com/dotnet/efcore."); + } + _relationalCommandBuilder - .Append(sqlConstantExpression.TypeMapping!.GenerateSqlLiteral(sqlConstantExpression.Value), sqlConstantExpression.IsSensitive); + .Append(sqlConstantExpression.TypeMapping.GenerateSqlLiteral(sqlConstantExpression.Value), sqlConstantExpression.IsSensitive); return sqlConstantExpression; } @@ -663,6 +670,13 @@ protected virtual Expression VisitSqlConstant(SqlConstantExpression sqlConstantE /// The for which to generate SQL. protected virtual Expression VisitSqlParameter(SqlParameterExpression sqlParameterExpression) { + if (sqlParameterExpression.TypeMapping is null) + { + throw new UnreachableException( + $"SqlParameterExpression '{sqlParameterExpression.Name}' has no type mapping. " + + "Please file a bug report at https://github.com/dotnet/efcore."); + } + var name = sqlParameterExpression.Name; // Only add the parameter to the command the first time we see its (non-invariant) name, even though we may need to add its @@ -672,7 +686,7 @@ protected virtual Expression VisitSqlParameter(SqlParameterExpression sqlParamet _relationalCommandBuilder.AddParameter( sqlParameterExpression.InvariantName, _sqlGenerationHelper.GenerateParameterName(name), - sqlParameterExpression.TypeMapping!, + sqlParameterExpression.TypeMapping, sqlParameterExpression.IsNullable); _parameterNames.Add(name); } diff --git a/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs b/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs index c99d57e7f4e..57902a9b6a8 100644 --- a/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs @@ -152,9 +152,14 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp var value = rowValue.Values[j]; if (value.TypeMapping is null - && inferredTypeMappings[j] is { } inferredTypeMapping) + // Fall back to the default type mapping for the CLR type when no type mapping was inferred + // from usage context (e.g. SelectMany where the value column isn't referenced). + && (inferredTypeMappings[j] + ?? RelationalDependencies.TypeMappingSource.FindMapping( + value.Type, QueryCompilationContext.Model)) + is { } resolvedTypeMapping) { - value = _sqlExpressionFactory.ApplyTypeMapping(value, inferredTypeMapping); + value = _sqlExpressionFactory.ApplyTypeMapping(value, resolvedTypeMapping); } // We currently add explicit conversions on the first row (but not to the _ord column), to ensure that the inferred diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index f08d13fc970..970b1d9be67 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -550,6 +550,10 @@ OFFSET 0 LIMIT 2 """); } + public override async Task Inline_collection_SelectMany_with_unreferenced_collection_value() + => await Assert.ThrowsAsync( + () => base.Inline_collection_SelectMany_with_unreferenced_collection_value()); + // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/287 (Aggregates over subqueries return null result set) [CosmosCondition(CosmosCondition.IsNotLinuxEmulator)] public override async Task Parameter_collection_Count() diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 6b8a4dda9e8..de2384a5b3f 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -270,6 +270,11 @@ public virtual async Task Inline_collection_in_query_filter() Assert.Equal(2, result.Id); } + [ConditionalFact] // #38285 + public virtual Task Inline_collection_SelectMany_with_unreferenced_collection_value() + => AssertQuery( + ss => ss.Set().SelectMany(e => new[] { "a", "b" }.Select(k => e))); + [ConditionalFact] public virtual Task Parameter_collection_Count() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index b0f77b12b83..457feb34b1c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -478,6 +478,18 @@ SELECT COUNT(*) """); } + public override async Task Inline_collection_SelectMany_with_unreferenced_collection_value() + { + await base.Inline_collection_SelectMany_with_unreferenced_collection_value(); + + AssertSql( + """ +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] +CROSS APPLY (VALUES (CAST(N'a' AS nvarchar(max))), (N'b')) AS [v]([Value]) +"""); + } + public override async Task Parameter_collection_Count() { await base.Parameter_collection_Count(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs index 6bfe2463603..59813065a7c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs @@ -655,6 +655,18 @@ SELECT COUNT(*) """); } + public override async Task Inline_collection_SelectMany_with_unreferenced_collection_value() + { + await base.Inline_collection_SelectMany_with_unreferenced_collection_value(); + + AssertSql( + """ +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] +CROSS APPLY (VALUES (CAST(N'a' AS nvarchar(max))), (N'b')) AS [v]([Value]) +"""); + } + public override async Task Parameter_collection_Count() { await base.Parameter_collection_Count(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index d266ab765bf..17b95e7fb8c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -721,6 +721,18 @@ SELECT COUNT(*) """); } + public override async Task Inline_collection_SelectMany_with_unreferenced_collection_value() + { + await base.Inline_collection_SelectMany_with_unreferenced_collection_value(); + + AssertSql( + """ +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] +CROSS APPLY (VALUES (CAST(N'a' AS nvarchar(max))), (N'b')) AS [v]([Value]) +"""); + } + public override async Task Parameter_collection_Count() { await base.Parameter_collection_Count(); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 3b657bfab60..7a7ab310151 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -2053,6 +2053,12 @@ public override async Task Column_collection_SelectMany() SqliteStrings.ApplyNotSupported, (await Assert.ThrowsAsync(() => base.Column_collection_SelectMany())).Message); + public override async Task Inline_collection_SelectMany_with_unreferenced_collection_value() + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Inline_collection_SelectMany_with_unreferenced_collection_value())).Message); + public override async Task Column_collection_SelectMany_with_filter() => Assert.Equal( SqliteStrings.ApplyNotSupported,