diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 48f63e6c20c..2c9e9b6853d 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -420,8 +420,7 @@ ParameterTranslationMode.Constant or ParameterTranslationMode.MultipleParameters
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
@@ -431,11 +430,11 @@ ParameterTranslationMode.Constant or ParameterTranslationMode.MultipleParameters
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..e2851f99d60 100644
--- a/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
+++ b/src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
@@ -106,16 +106,7 @@ when TryGetInferredTypeMapping(columnExpression.TableAlias, columnExpression.Nam
case ValuesExpression valuesExpression:
// 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 +126,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 +136,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 +146,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 +164,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 +185,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 a279f21889f..447fe6daf3c 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, TranslationMode, typeMapping);
+ public SqlParameterExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping)
+ => new(InvariantName, Name, Type, IsNullable, TranslationMode, typeMapping);
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index 7d828280c86..644ebeb3438 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -122,7 +122,7 @@ protected override Expression VisitExtension(Expression node)
var intTypeMapping = (IntTypeMapping?)Dependencies.TypeMappingSource.FindMapping(typeof(int));
Check.DebugAssert(intTypeMapping is not null);
- var valuesOrderingCounter = 1;
+ var valuesOrderingCounter = 0;
var processedValues = new List();
@@ -151,13 +151,26 @@ protected override Expression VisitExtension(Expression node)
case ParameterTranslationMode.Constant:
{
- foreach (var value in values)
+ for (var i = 0; i < values.Count; i++)
{
+ var value = _sqlExpressionFactory.Constant(
+ values[i],
+ values[i]?.GetType() ?? typeof(object),
+ sensitive: true,
+ elementTypeMapping);
+
+ // We currently add explicit conversions on the first row (but not to the _ord column), to ensure that the inferred
+ // types are properly typed. See #30605 for removing that when not needed.
+ if (i == 0)
+ {
+ value = new SqlUnaryExpression(ExpressionType.Convert, value, value.Type, value.TypeMapping);
+ }
+
processedValues.Add(
new RowValueExpression(
ProcessValuesOrderingColumn(
valuesExpression,
- [_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), sensitive: true, elementTypeMapping)],
+ [value],
intTypeMapping,
ref valuesOrderingCounter)));
}
@@ -1497,9 +1510,11 @@ protected virtual SqlExpression VisitJsonScalar(
bool allowOptimizedExpansion,
out bool nullable)
{
- nullable = jsonScalarExpression.IsNullable;
+ var json = Visit(jsonScalarExpression.Json, out var jsonNullable);
+
+ nullable = jsonNullable || jsonScalarExpression.IsNullable;
- return jsonScalarExpression;
+ return jsonScalarExpression.Update(json);
}
///
diff --git a/src/EFCore.Relational/Query/SqlTreePruner.cs b/src/EFCore.Relational/Query/SqlTreePruner.cs
index bc80eac58b3..27c8e0301ed 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,117 @@ 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)
+ {
+ BitArray? referencedColumns = null;
+ List? newColumnNames = null;
+
+ if (ReferencedColumnMap.TryGetValue(values.Alias, out var referencedColumnNames))
+ {
+ // 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.
+ 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;
+ }
+ }
+ }
+ else
+ {
+ // No columns were referenced at all on this ValuesExpression.
+ // This happens in some edge cases, e.g. there's a simple COUNT(*) over it (and so no specific columns are referenced).
+ // We can prune all columns but need to leave one, so that the ValuesExpression is still valid.
+ // Pick the first column, unless it happens to be the _ord column, in which case we pick the second one.
+ referencedColumns = new BitArray(values.ColumnNames.Count);
+ newColumnNames = new List(1);
+
+ if (values.ColumnNames[0] is RelationalQueryableMethodTranslatingExpressionVisitor.ValuesOrderingColumnName)
+ {
+ referencedColumns[1] = true;
+ newColumnNames.Add(values.ColumnNames[1]);
+ }
+ else
+ {
+ referencedColumns[0] = true;
+ newColumnNames.Add(values.ColumnNames[0]);
+ }
+ }
+
+ 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 5cc00ae89ae..bec3d0e4134 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
@@ -1020,6 +1020,13 @@ FROM root c
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ var exception = await Assert.ThrowsAsync(() => base.Inline_collection_index_Column_with_EF_Constant());
+
+ Assert.Equal(CoreStrings.EFConstantNotSupported, exception.Message);
+ }
+
public override async Task Inline_collection_value_index_Column()
{
// Member indexer (c.Array[c.SomeMember]) isn't supported by Cosmos
diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
index 1cdc5d16e96..9d47e240d0b 100644
--- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
@@ -680,6 +680,16 @@ public virtual Task Inline_collection_index_Column()
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));
+ [ConditionalFact]
+ public virtual Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ int[] ints = [1, 2, 3];
+
+ return AssertQuery(
+ ss => ss.Set().Where(c => EF.Constant(ints)[c.Int] == 1),
+ ss => ss.Set().Where(c => (c.Int <= 2 ? ints[c.Int] : -1) == 1));
+ }
+
[ConditionalFact]
public virtual Task Inline_collection_value_index_Column()
=> AssertQuery(
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
index e5d2742eccd..9d4e2768fc0 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs
@@ -2604,7 +2604,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])
""",
//
@@ -2628,7 +2628,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/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index 5dd4ae31322..71867841ae7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -9194,7 +9194,7 @@ FROM [Weapons] AS [w]
) AS [w0]
WHERE [w0].[row] <= ISNULL((
SELECT [n].[Value]
- FROM (VALUES (1, @numbers1), (2, @numbers2), (3, @numbers3)) AS [n]([_ord], [Value])
+ FROM (VALUES (@numbers1), (@numbers2), (@numbers3)) AS [n]([Value])
ORDER BY [n].[Value]
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0)
) AS [w1] ON [g].[FullName] = [w1].[OwnerFullName]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
index 865b66dabb5..50d83b6bee7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
@@ -799,7 +799,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
""");
break;
@@ -905,7 +905,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 4d85180064d..52d1624599e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
@@ -790,7 +790,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)
""");
}
@@ -805,7 +805,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
""");
}
@@ -900,6 +900,22 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant();
+
+ 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()
{
await base.Inline_collection_value_index_Column();
@@ -946,7 +962,7 @@ public override async Task Parameter_collection_index_Column_equal_Column()
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT [i].[Value]
- FROM (VALUES (1, @ints1), (2, @ints2), (3, @ints3)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2), (2, @ints3)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = [p].[Int]
""");
@@ -966,7 +982,7 @@ public override async Task Parameter_collection_index_Column_equal_constant()
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT [i].[Value]
- FROM (VALUES (1, @ints1), (2, @ints2), (3, @ints3)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2), (2, @ints3)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET [p].[Int] ROWS FETCH NEXT 1 ROWS ONLY) = 1
""");
@@ -1141,7 +1157,7 @@ FROM [PrimitiveCollectionsEntity] AS [p]
SELECT COUNT(*)
FROM (
SELECT [i].[Value] AS [Value0]
- FROM (VALUES (1, @ints1), (2, @ints2)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET 1 ROWS
) AS [i0]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
index 061226598ea..c11fb0aac04 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,18 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant();
+
+ 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 CAST(JSON_VALUE(N'[1,2,3]', '$[' + CAST([p].[Int] AS nvarchar(max)) + ']') AS int) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column()
{
await base.Inline_collection_value_index_Column();
@@ -1664,7 +1676,7 @@ SELECT COUNT(*)
SELECT [i1].[Value]
FROM (
SELECT [i].[Value]
- FROM (VALUES (1, @ints1), (2, @ints2)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET 1 ROWS
) AS [i1]
@@ -1754,7 +1766,7 @@ FROM [PrimitiveCollectionsEntity] AS [p]
SELECT COUNT(*)
FROM (
SELECT [i].[Value] AS [Value0]
- FROM (VALUES (1, @ints1), (2, @ints2)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET 1 ROWS
) AS [i0]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
index 540a875ecd7..f2f1a5425d4 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()
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant();
+
+ 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()
{
await base.Inline_collection_value_index_Column();
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
index 597ee0976cd..0583fd68035 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,18 @@ ORDER BY [v].[_ord]
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant();
+
+ 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 CAST(JSON_VALUE(N'[1,2,3]', '$[' + CAST([p].[Int] AS nvarchar(max)) + ']') AS int) = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column()
{
await base.Inline_collection_value_index_Column();
@@ -1688,7 +1700,7 @@ SELECT COUNT(*)
SELECT [i1].[Value]
FROM (
SELECT [i].[Value]
- FROM (VALUES (1, @ints1), (2, @ints2)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET 1 ROWS
) AS [i1]
@@ -1778,7 +1790,7 @@ FROM [PrimitiveCollectionsEntity] AS [p]
SELECT COUNT(*)
FROM (
SELECT [i].[Value] AS [Value0]
- FROM (VALUES (1, @ints1), (2, @ints2)) AS [i]([_ord], [Value])
+ FROM (VALUES (0, @ints1), (1, @ints2)) AS [i]([_ord], [Value])
ORDER BY [i].[_ord]
OFFSET 1 ROWS
) AS [i0]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
index f1c31a3f0a0..5f392ce814e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
@@ -12271,7 +12271,7 @@ FROM [Weapons] AS [w]
) AS [w0]
WHERE [w0].[row] <= ISNULL((
SELECT [n].[Value]
- FROM (VALUES (1, @numbers1), (2, @numbers2), (3, @numbers3)) AS [n]([_ord], [Value])
+ FROM (VALUES (@numbers1), (@numbers2), (@numbers3)) AS [n]([Value])
ORDER BY [n].[Value]
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0)
) AS [w1] ON [u].[FullName] = [w1].[OwnerFullName]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
index 0bb1721925f..c6fbaa27acc 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
@@ -10412,7 +10412,7 @@ FROM [Weapons] AS [w]
) AS [w0]
WHERE [w0].[row] <= ISNULL((
SELECT [n].[Value]
- FROM (VALUES (1, @numbers1), (2, @numbers2), (3, @numbers3)) AS [n]([_ord], [Value])
+ FROM (VALUES (@numbers1), (@numbers2), (@numbers3)) AS [n]([Value])
ORDER BY [n].[Value]
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0)
) AS [w1] ON [g].[FullName] = [w1].[OwnerFullName]
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
index bf8f65d8133..d02c75168d9 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
@@ -9086,7 +9086,7 @@ LEFT JOIN (
) AS [w0]
WHERE [w0].[row] <= ISNULL((
SELECT [n].[Value]
- FROM (VALUES (1, @numbers1), (2, @numbers2), (3, @numbers3)) AS [n]([_ord], [Value])
+ FROM (VALUES (@numbers1), (@numbers2), (@numbers3)) AS [n]([Value])
ORDER BY [n].[Value]
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0)
) AS [w1] ON [g].[FullName] = [w1].[OwnerFullName]
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocMiscellaneousQuerySqliteTest.cs
index f80084d99e5..196afbe559c 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/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
index 728045eb5c2..718a6af99a1 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
@@ -8750,7 +8750,7 @@ LEFT JOIN (
) AS "w0"
WHERE "w0"."row" <= COALESCE((
SELECT "n"."Value"
- FROM (SELECT 1 AS "_ord", @numbers1 AS "Value" UNION ALL VALUES (2, @numbers2), (3, @numbers3)) AS "n"
+ FROM (SELECT @numbers1 AS "Value" UNION ALL VALUES (@numbers2), (@numbers3)) AS "n"
ORDER BY "n"."Value"
LIMIT 1 OFFSET 1), 0)
) AS "w1" ON "g"."FullName" = "w1"."OwnerFullName"
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
index 2ec5a9e8044..be157b31860 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
@@ -343,7 +343,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
""");
break;
@@ -449,7 +449,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 fb1ae264fae..6f92d944a02 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,18 @@ ORDER BY "v"."_ord"
""");
}
+ public override async Task Inline_collection_index_Column_with_EF_Constant()
+ {
+ await base.Inline_collection_index_Column_with_EF_Constant();
+
+ 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 '[1,2,3]' ->> "p"."Int" = 1
+""");
+ }
+
public override async Task Inline_collection_value_index_Column()
{
// SQLite doesn't support correlated subqueries where the outer column is used as the LIMIT/OFFSET (see OFFSET "p"."Int" below)
@@ -1639,7 +1651,7 @@ SELECT COUNT(*)
SELECT COUNT(*)
FROM (
SELECT "i"."Value" AS "Value0"
- FROM (SELECT 1 AS "_ord", @ints1 AS "Value" UNION ALL VALUES (2, @ints2)) AS "i"
+ FROM (SELECT 0 AS "_ord", @ints1 AS "Value" UNION ALL VALUES (1, @ints2)) AS "i"
ORDER BY "i"."_ord"
LIMIT -1 OFFSET 1
) AS "i0"
@@ -1671,7 +1683,7 @@ SELECT COUNT(*)
SELECT "i1"."Value"
FROM (
SELECT "i"."Value"
- FROM (SELECT 1 AS "_ord", @ints1 AS "Value" UNION ALL VALUES (2, @ints2)) AS "i"
+ FROM (SELECT 0 AS "_ord", @ints1 AS "Value" UNION ALL VALUES (1, @ints2)) AS "i"
ORDER BY "i"."_ord"
LIMIT -1 OFFSET 1
) AS "i1"