diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 8db287f17ee..e587fe12e3a 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -250,7 +250,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
}
else
{
- _relationalCommandBuilder.Append("1");
+ GenerateEmptyProjection(selectExpression);
}
if (selectExpression.Tables.Any())
@@ -309,6 +309,15 @@ protected virtual void GeneratePseudoFromClause()
{
}
+ ///
+ /// Generates empty projection for a SelectExpression.
+ ///
+ /// SelectExpression for which the empty projection will be generated.
+ protected virtual void GenerateEmptyProjection(SelectExpression selectExpression)
+ {
+ _relationalCommandBuilder.Append("1");
+ }
+
///
protected override Expression VisitProjection(ProjectionExpression projectionExpression)
{
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
index e318339c842..c681adb4627 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs
@@ -16,6 +16,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
///
public class SqlServerQuerySqlGenerator : QuerySqlGenerator
{
+ private static readonly bool UseOldBehavior29667
+ = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue29667", out var enabled29667) && enabled29667;
+
private readonly IRelationalTypeMappingSource _typeMappingSource;
///
@@ -72,6 +75,21 @@ protected override Expression VisitDelete(DeleteExpression deleteExpression)
RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteDelete)));
}
+ ///
+ /// 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.
+ ///
+ protected override void GenerateEmptyProjection(SelectExpression selectExpression)
+ {
+ base.GenerateEmptyProjection(selectExpression);
+ if (!UseOldBehavior29667 && selectExpression.Alias != null)
+ {
+ Sql.Append(" AS empty");
+ }
+ }
+
///
/// 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
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindKeylessEntitiesQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindKeylessEntitiesQueryCosmosTest.cs
index efda76c9a06..52bf66aa0cf 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindKeylessEntitiesQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindKeylessEntitiesQueryCosmosTest.cs
@@ -171,6 +171,30 @@ FROM root c
""");
}
+ public override async Task Count_over_keyless_entity(bool async)
+ {
+ await base.Count_over_keyless_entity(async);
+
+ AssertSql(
+"""
+SELECT COUNT(1) AS c
+FROM root c
+WHERE (c["Discriminator"] = "Customer")
+""");
+ }
+
+ public override async Task Count_over_keyless_entity_with_pushdown(bool async)
+ {
+ // Cosmos client evaluation. Issue #17246.
+ await AssertTranslationFailed(() => base.Count_over_keyless_entity_with_pushdown(async));
+ }
+
+ public override async Task Count_over_keyless_entity_with_pushdown_empty_projection(bool async)
+ {
+ // Cosmos client evaluation. Issue #17246.
+ await AssertTranslationFailed(() => base.Count_over_keyless_entity_with_pushdown_empty_projection(async));
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
index 30aecb7fa77..888b9fed440 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
@@ -167,4 +167,25 @@ public virtual Task Collection_correlated_with_keyless_entity_in_predicate_works
.Select(pv => new { pv.City, pv.ContactName })
.OrderBy(x => x.ContactName)
.Take(2));
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Count_over_keyless_entity(bool async)
+ => AssertCount(
+ async,
+ ss => ss.Set());
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Count_over_keyless_entity_with_pushdown(bool async)
+ => AssertCount(
+ async,
+ ss => ss.Set().OrderBy(x => x.ContactTitle).Take(10));
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Count_over_keyless_entity_with_pushdown_empty_projection(bool async)
+ => AssertCount(
+ async,
+ ss => ss.Set().Take(10));
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
index b8d928af77d..16b49821dcf 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
@@ -232,6 +232,56 @@ public override async Task KeylessEntity_with_included_nav(bool async)
""");
}
+ public override async Task Count_over_keyless_entity(bool async)
+ {
+ await base.Count_over_keyless_entity(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM (
+ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c]
+) AS [m]
+""");
+ }
+
+ public override async Task Count_over_keyless_entity_with_pushdown(bool async)
+ {
+ await base.Count_over_keyless_entity_with_pushdown(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT COUNT(*)
+FROM (
+ SELECT TOP(@__p_0) [m].[ContactTitle]
+ FROM (
+ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c]
+ ) AS [m]
+ ORDER BY [m].[ContactTitle]
+) AS [t]
+""");
+ }
+
+ public override async Task Count_over_keyless_entity_with_pushdown_empty_projection(bool async)
+ {
+ await base.Count_over_keyless_entity_with_pushdown_empty_projection(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT COUNT(*)
+FROM (
+ SELECT TOP(@__p_0) 1 AS empty
+ FROM (
+ SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c]
+ ) AS [m]
+) AS [t]
+""");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);