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,