diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 848973552c6..7203bd2ddd1 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -405,8 +405,7 @@ JsonScalarExpression jsonScalar
var sqlExpression = sqlExpressions[i];
rowExpressions[i] =
new RowValueExpression(
- new[]
- {
+ [
// Since VALUES may not guarantee row ordering, we add an _ord value by which we'll order.
_sqlExpressionFactory.Constant(i, intTypeMapping),
// If no type mapping was inferred (i.e. no column in the inline collection), it's left null, to allow it to get
@@ -416,11 +415,11 @@ JsonScalarExpression jsonScalar
sqlExpression.TypeMapping is null && inferredTypeMaping is not null
? _sqlExpressionFactory.ApplyTypeMapping(sqlExpression, inferredTypeMaping)
: sqlExpression
- });
+ ]);
}
var alias = _sqlAliasManager.GenerateTableAlias("values");
- var valuesExpression = new ValuesExpression(alias, rowExpressions, new[] { ValuesOrderingColumnName, ValuesValueColumnName });
+ var valuesExpression = new ValuesExpression(alias, rowExpressions, [ValuesOrderingColumnName, ValuesValueColumnName]);
return CreateShapedQueryExpressionForValuesExpression(
valuesExpression,
diff --git a/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs b/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
index 926e75ce5f5..0f3862456b3 100644
--- a/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
+++ b/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
@@ -107,15 +107,7 @@ when TryGetInferredTypeMapping(columnExpression.TableAlias, columnExpression.Nam
// By default, the ValuesExpression also contains an ordering by a synthetic increasing _ord. If the containing
// SelectExpression doesn't project it out or require it (limit/offset), strip that out.
// TODO: Strictly-speaking, stripping the ordering doesn't belong in this visitor which is about applying type mappings
- return ApplyTypeMappingsOnValuesExpression(
- valuesExpression,
- stripOrdering: _currentSelectExpression is { Limit: null, Offset: null }
- && !_currentSelectExpression.Projection.Any(
- p => p.Expression is ColumnExpression
- {
- Name: RelationalQueryableMethodTranslatingExpressionVisitor.ValuesOrderingColumnName
- } c
- && c.TableAlias == valuesExpression.Alias));
+ return ApplyTypeMappingsOnValuesExpression(valuesExpression);
// SqlExpressions without an inferred type mapping indicates a problem in EF - everything should have been inferred.
// One exception is SqlFragmentExpression, which never has a type mapping.
@@ -135,8 +127,7 @@ when TryGetInferredTypeMapping(columnExpression.TableAlias, columnExpression.Nam
/// As an optimization, it can also strip the first _ord column if it's determined that it isn't needed (most cases).
///
/// The to apply the mappings to.
- /// Whether to strip the _ord column.
- protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExpression valuesExpression, bool stripOrdering)
+ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExpression valuesExpression)
{
var inferredTypeMappings = TryGetInferredTypeMapping(
valuesExpression.Alias, RelationalQueryableMethodTranslatingExpressionVisitor.ValuesValueColumnName, out var typeMapping)
@@ -146,9 +137,6 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp
Check.DebugAssert(
valuesExpression.ColumnNames[0] == RelationalQueryableMethodTranslatingExpressionVisitor.ValuesOrderingColumnName,
"First ValuesExpression column isn't the ordering column");
- var newColumnNames = stripOrdering
- ? valuesExpression.ColumnNames.Skip(1).ToArray()
- : valuesExpression.ColumnNames;
switch (valuesExpression)
{
@@ -159,14 +147,9 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp
for (var i = 0; i < newRowValues.Length; i++)
{
var rowValue = rowValues[i];
- var newValues = new SqlExpression[newColumnNames.Count];
+ var newValues = new SqlExpression[valuesExpression.ColumnNames.Count];
for (var j = 0; j < valuesExpression.ColumnNames.Count; j++)
{
- if (j == 0 && stripOrdering)
- {
- continue;
- }
-
var value = rowValue.Values[j];
if (value.TypeMapping is null
@@ -182,13 +165,13 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp
value = new SqlUnaryExpression(ExpressionType.Convert, value, value.Type, value.TypeMapping);
}
- newValues[j - (stripOrdering ? 1 : 0)] = value;
+ newValues[j] = value;
}
newRowValues[i] = new RowValueExpression(newValues);
}
- return new ValuesExpression(valuesExpression.Alias, newRowValues, null, newColumnNames);
+ return valuesExpression.Update(newRowValues);
}
// VALUES over a values parameter (i.e. a parameter representing the entire collection, that will be constantized into the SQL
@@ -203,10 +186,7 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp
throw new UnreachableException("A RelationalTypeMapping collection type mapping could not be found");
}
- return new ValuesExpression(
- valuesExpression.Alias,
- (SqlParameterExpression)valuesParameter.ApplyTypeMapping(collectionParameterTypeMapping),
- newColumnNames);
+ return valuesExpression.Update(valuesParameter.ApplyTypeMapping(collectionParameterTypeMapping));
}
default:
diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
index 48c1602fbaa..bf9de0d0db4 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
@@ -74,8 +74,8 @@ public SqlParameterExpression(
///
/// A relational type mapping to apply.
/// A new expression which has supplied type mapping.
- public SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping)
- => new SqlParameterExpression(InvariantName, Name, Type, IsNullable, ShouldBeConstantized, typeMapping);
+ public SqlParameterExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping)
+ => new(InvariantName, Name, Type, IsNullable, ShouldBeConstantized, typeMapping);
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
diff --git a/src/EFCore.Relational/Query/SqlTreePruner.cs b/src/EFCore.Relational/Query/SqlTreePruner.cs
index bc80eac58b3..756aaea423c 100644
--- a/src/EFCore.Relational/Query/SqlTreePruner.cs
+++ b/src/EFCore.Relational/Query/SqlTreePruner.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace Microsoft.EntityFrameworkCore.Query;
@@ -117,6 +118,9 @@ protected override Expression VisitExtension(Expression node)
PruneSelect(source2, preserveProjection: true));
}
+ case ValuesExpression values:
+ return PruneValues(values);
+
default:
return base.VisitExtension(node);
}
@@ -262,4 +266,98 @@ protected virtual SelectExpression PruneSelect(SelectExpression select, bool pre
return select.Update(
tables ?? select.Tables, predicate, groupBy, having, projections ?? select.Projection, orderings, offset, limit);
}
+
+ ///
+ /// Prunes a , removing columns inside it which aren't referenced.
+ /// This currently removes the _ord column that gets added to preserve ordering, for cases where
+ /// that ordering isn't actually necessary.
+ ///
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual ValuesExpression PruneValues(ValuesExpression values)
+ {
+ if (!ReferencedColumnMap.TryGetValue(values.Alias, out var referencedColumnNames))
+ {
+ throw new UnreachableException("The case of a totally unreferenced ValuesExpression is already handled for all tables in PruneSelect.");
+ }
+
+ // First, build a bitmap of which columns are referenced which we can efficiently access later
+ // as we traverse the rows. At the same time, build a list of the names of the referenced columns.
+ BitArray? referencedColumns = null;
+ List? newColumnNames = null;
+ for (var i = 0; i < values.ColumnNames.Count; i++)
+ {
+ var columnName = values.ColumnNames[i];
+ var isColumnReferenced = referencedColumnNames.Contains(columnName);
+
+ if (newColumnNames is null && !isColumnReferenced)
+ {
+ newColumnNames = new List(values.ColumnNames.Count);
+ referencedColumns = new BitArray(values.ColumnNames.Count);
+
+ for (var j = 0; j < i; j++)
+ {
+ referencedColumns[j] = true;
+ newColumnNames.Add(columnName);
+ }
+ }
+
+ if (newColumnNames is not null)
+ {
+ if (isColumnReferenced)
+ {
+ newColumnNames.Add(columnName);
+ }
+
+ referencedColumns![i] = isColumnReferenced;
+ }
+ }
+
+ if (referencedColumns is null)
+ {
+ return values;
+ }
+
+ // We know at least some columns are getting pruned.
+ Debug.Assert(newColumnNames is not null);
+
+ switch (values)
+ {
+ // If we have a value parameter (row values aren't specific in line), we still prune the column names.
+ // Later in SqlNullabilityProcessor, when the parameterized collection is inline to constants, we'll take
+ // the column names into account.
+ case ValuesExpression { ValuesParameter: not null }:
+ return new ValuesExpression(values.Alias, rowValues: null, values.ValuesParameter, newColumnNames);
+
+ // Go over the rows and create new ones without the pruned columns.
+ case ValuesExpression { RowValues: IReadOnlyList rowValues }:
+ var newRowValues = new RowValueExpression[rowValues.Count];
+
+ for (var i = 0; i < rowValues.Count; i++)
+ {
+ var oldValues = rowValues[i].Values;
+ var newValues = new List(newColumnNames.Count);
+
+ for (var j = 0; j < values.ColumnNames.Count; j++)
+ {
+ if (referencedColumns[j])
+ {
+ newValues.Add(oldValues[j]);
+ }
+ }
+
+ newRowValues[i] = new RowValueExpression(newValues);
+ }
+
+ return new ValuesExpression(values.Alias, newRowValues, valuesParameter: null, newColumnNames);
+
+ default:
+ throw new UnreachableException();
+ }
+ }
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
index 6b09305e63d..3cff348a854 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
@@ -1158,6 +1158,17 @@ FROM root c
}
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ // Always throws for sync.
+ if (async)
+ {
+ var exception = await Assert.ThrowsAsync(() => base.Inline_collection_index_Column_with_EF_Constant(async));
+
+ Assert.Equal(CoreStrings.EFConstantNotSupported, exception.Message);
+ }
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
// Always throws for sync.
diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
index 6c4b2596051..fb57278b48e 100644
--- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
@@ -849,6 +849,18 @@ public virtual Task Inline_collection_index_Column(bool async)
ss => ss.Set().Where(c => new[] { 1, 2, 3 }[c.Int] == 1),
ss => ss.Set().Where(c => (c.Int <= 2 ? new[] { 1, 2, 3 }[c.Int] : -1) == 1));
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ int[] ints = [1, 2, 3];
+
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(c => EF.Constant(ints)[c.Int] == 1),
+ ss => ss.Set().Where(c => (c.Int <= 2 ? ints[c.Int] : -1) == 1));
+ }
+
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Inline_collection_value_index_Column(bool async)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
index cc0a312389c..bb88af3dad2 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
@@ -2505,7 +2505,7 @@ WHERE [t].[Id] IN (?, ?, ?)
FROM [TestEntities] AS [t]
WHERE EXISTS (
SELECT 1
- FROM (VALUES (?), (?), (?)) AS [i]([Value])
+ FROM (VALUES (CAST(? AS int)), (?), (?)) AS [i]([Value])
WHERE [i].[Value] = [t].[Id])
""",
//
@@ -2529,7 +2529,7 @@ WHERE [t].[Id] IN (1, 2, 3)
FROM [TestEntities] AS [t]
WHERE EXISTS (
SELECT 1
- FROM (VALUES (1), (2), (3)) AS [i]([Value])
+ FROM (VALUES (CAST(1 AS int)), (2), (3)) AS [i]([Value])
WHERE [i].[Value] = [t].[Id])
""",
//
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
index 3016f7c7b93..ec2060c523e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
@@ -795,7 +795,7 @@ SELECT [t].[Id]
FROM [TestEntity] AS [t]
WHERE (
SELECT COUNT(*)
- FROM (VALUES (2), (999)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999)) AS [i]([Value])
WHERE [i].[Value] > [t].[Id]) = 1
""");
}
@@ -890,7 +890,7 @@ SELECT [t].[Id]
FROM [TestEntity] AS [t]
WHERE (
SELECT COUNT(*)
- FROM (VALUES (2), (999)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999)) AS [i]([Value])
WHERE [i].[Value] > [t].[Id]) = 1
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
index e2677da66c7..4c8e1ac47bd 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
@@ -791,7 +791,7 @@ public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE EXISTS (
SELECT 1
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > 0)
""");
}
@@ -806,7 +806,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > [p].[Id]) = 2
""");
}
@@ -901,6 +901,22 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant(async);
+
+ 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]
+WHERE (
+ SELECT [i].[Value]
+ FROM (VALUES (0, CAST(1 AS int)), (1, 2), (2, 3)) AS [i]([_ord], [Value])
+ ORDER BY [i].[_ord]
+ OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
await base.Inline_collection_value_index_Column(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
index b57e0e025c2..c4046172e8c 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
@@ -784,7 +784,7 @@ public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE EXISTS (
SELECT 1
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > 0)
""");
}
@@ -799,7 +799,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > [p].[Id]) = 2
""");
}
@@ -1066,6 +1066,22 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant(async);
+
+ 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]
+WHERE (
+ SELECT [i].[Value]
+ FROM (VALUES (0, CAST(1 AS int)), (1, 2), (2, 3)) AS [i]([_ord], [Value])
+ ORDER BY [i].[_ord]
+ OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
await base.Inline_collection_value_index_Column(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
index 338bad1c1ff..23be1be6d54 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
@@ -73,11 +73,11 @@ public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any
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].[String], [p].[Strings]
+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 EXISTS (
SELECT 1
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > 0)
""");
}
@@ -1095,6 +1095,22 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant(async);
+
+ 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]
+WHERE (
+ SELECT [i].[Value]
+ FROM (VALUES (0, CAST(1 AS int)), (1, 2), (2, 3)) AS [i]([_ord], [Value])
+ ORDER BY [i].[_ord]
+ OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
await base.Inline_collection_value_index_Column(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
index c0164a1fe15..279b5910ce4 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
@@ -807,7 +807,7 @@ public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE EXISTS (
SELECT 1
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > 0)
""");
}
@@ -822,7 +822,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
- FROM (VALUES (2), (999), (1000)) AS [i]([Value])
+ FROM (VALUES (CAST(2 AS int)), (999), (1000)) AS [i]([Value])
WHERE [i].[Value] > [p].[Id]) = 2
""");
}
@@ -1089,6 +1089,22 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant(async);
+
+ 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]
+WHERE (
+ SELECT [i].[Value]
+ FROM (VALUES (0, CAST(1 AS int)), (1, 2), (2, 3)) AS [i]([_ord], [Value])
+ ORDER BY [i].[_ord]
+ OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
await base.Inline_collection_value_index_Column(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs
index e878daf1ff8..40c7640c0a1 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs
@@ -108,7 +108,7 @@ public override async Task Check_inlined_constants_redacting(bool async, bool en
FROM "TestEntities" AS "t"
WHERE EXISTS (
SELECT 1
- FROM (SELECT ? AS "Value" UNION ALL VALUES (?), (?)) AS "i"
+ FROM (SELECT CAST(? AS INTEGER) AS "Value" UNION ALL VALUES (?), (?)) AS "i"
WHERE "i"."Value" = "t"."Id")
""",
//
@@ -132,7 +132,7 @@ SELECT 1
FROM "TestEntities" AS "t"
WHERE EXISTS (
SELECT 1
- FROM (SELECT 1 AS "Value" UNION ALL VALUES (2), (3)) AS "i"
+ FROM (SELECT CAST(1 AS INTEGER) AS "Value" UNION ALL VALUES (2), (3)) AS "i"
WHERE "i"."Value" = "t"."Id")
""",
//
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
index fbddf89fa4f..f444383ffac 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
@@ -339,7 +339,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM "TestEntity" AS "t"
WHERE (
SELECT COUNT(*)
- FROM (SELECT 2 AS "Value" UNION ALL VALUES (999)) AS "i"
+ FROM (SELECT CAST(2 AS INTEGER) AS "Value" UNION ALL VALUES (999)) AS "i"
WHERE "i"."Value" > "t"."Id") = 1
""");
}
@@ -434,7 +434,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM "TestEntity" AS "t"
WHERE (
SELECT COUNT(*)
- FROM (SELECT 2 AS "Value" UNION ALL VALUES (999)) AS "i"
+ FROM (SELECT CAST(2 AS INTEGER) AS "Value" UNION ALL VALUES (999)) AS "i"
WHERE "i"."Value" > "t"."Id") = 1
""");
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
index cc161920c78..e9194f6b969 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
@@ -795,7 +795,7 @@ public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any
FROM "PrimitiveCollectionsEntity" AS "p"
WHERE EXISTS (
SELECT 1
- FROM (SELECT 2 AS "Value" UNION ALL VALUES (999), (1000)) AS "i"
+ FROM (SELECT CAST(2 AS INTEGER) AS "Value" UNION ALL VALUES (999), (1000)) AS "i"
WHERE "i"."Value" > 0)
""");
}
@@ -810,7 +810,7 @@ public override async Task Parameter_collection_Count_with_column_predicate_with
FROM "PrimitiveCollectionsEntity" AS "p"
WHERE (
SELECT COUNT(*)
- FROM (SELECT 2 AS "Value" UNION ALL VALUES (999), (1000)) AS "i"
+ FROM (SELECT CAST(2 AS INTEGER) AS "Value" UNION ALL VALUES (999), (1000)) AS "i"
WHERE "i"."Value" > "p"."Id") = 2
""");
}
@@ -1054,6 +1054,23 @@ ORDER BY "v"."_ord"
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant(bool async)
+ {
+ // SQLite doesn't support correlated subqueries where the outer column is used as the LIMIT/OFFSET (see OFFSET "p"."Int" below)
+ await Assert.ThrowsAsync(() => base.Inline_collection_index_Column_with_EF_Constant(async));
+
+ 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"
+WHERE (
+ SELECT "i"."Value"
+ FROM (SELECT 0 AS "_ord", CAST(1 AS INTEGER) AS "Value" UNION ALL VALUES (1, 2), (2, 3)) AS "i"
+ ORDER BY "i"."_ord"
+ LIMIT 1 OFFSET "p"."Int") = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column(bool async)
{
// SQLite doesn't support correlated subqueries where the outer column is used as the LIMIT/OFFSET (see OFFSET "p"."Int" below)