diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs index f3c07ea482d..07950207ab2 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs @@ -766,7 +766,7 @@ when sqlParameterExpression.Name.StartsWith(QueryCompilationContext.QueryParamet var newParameterName = $"{_runtimeParameterPrefix}" - + $"{sqlParameterExpression.Name.Substring(QueryCompilationContext.QueryParameterPrefix.Length)}_{property.Name}"; + + $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}"; rewrittenSource = _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); break; @@ -880,7 +880,7 @@ when sqlParameterExpression.Name.StartsWith(QueryCompilationContext.QueryParamet var newParameterName = $"{_runtimeParameterPrefix}" - + $"{sqlParameterExpression.Name.Substring(QueryCompilationContext.QueryParameterPrefix.Length)}_{property.Name}"; + + $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}"; return _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 28b51c1a12a..dd242f435a5 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1254,7 +1254,7 @@ when sqlParameterExpression.Name.StartsWith(QueryCompilationContext.QueryParamet var newParameterName = $"{RuntimeParameterPrefix}" - + $"{sqlParameterExpression.Name.Substring(QueryCompilationContext.QueryParameterPrefix.Length)}_{property.Name}"; + + $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}"; return _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); diff --git a/src/EFCore/Diagnostics/LoggerCategory.cs b/src/EFCore/Diagnostics/LoggerCategory.cs index ffbea7e78a6..1dbeb8e0d97 100644 --- a/src/EFCore/Diagnostics/LoggerCategory.cs +++ b/src/EFCore/Diagnostics/LoggerCategory.cs @@ -40,7 +40,7 @@ private static string ToName(Type loggerCategoryType) var index = name.IndexOf(outerClassName, StringComparison.Ordinal); if (index >= 0) { - name = name.Substring(0, index) + name.Substring(index + outerClassName.Length); + name = name.Substring(0, index) + name[(index + outerClassName.Length)..]; } return name; diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 24e63224664..9cfaa8009c1 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2300,6 +2300,12 @@ public static string QueryRootDifferentEntityType(object? entityType) GetString("QueryRootDifferentEntityType", nameof(entityType)), entityType); + /// + /// Translation of 'Select' which contains grouping parameter without composition is not supported. + /// + public static string QuerySelectContainsGrouping + => GetString("QuerySelectContainsGrouping"); + /// /// Translation of '{expression}' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 3bf20ef9fbd..3ce0abdc359 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1313,6 +1313,9 @@ The replacement entity type: {entityType} does not have same name and CLR type as entity type this query root represents. + + Translation of 'Select' which contains grouping parameter without composition is not supported. + Translation of '{expression}' failed. Either the query source is not an entity type, or the specified property does not exist on the entity type. diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index d56b3af48f8..a36e224d44e 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -1144,14 +1144,28 @@ public GroupingElementReplacingExpressionVisitor( _cloningExpressionVisitor = new CloningExpressionVisitor(); } + public bool ContainsGrouping { get; private set; } + [return: NotNullIfNotNull("expression")] public override Expression? Visit(Expression? expression) { if (expression == _parameterExpression) + { + ContainsGrouping = true; + } + + return base.Visit(expression); + } + + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.AsQueryable + && methodCallExpression.Arguments[0] == _parameterExpression) { var currentTree = _cloningExpressionVisitor.Clone(_navigationExpansionExpression.CurrentTree); - return new NavigationExpansionExpression( + var navigationExpansionExpression = new NavigationExpansionExpression( _navigationExpansionExpression.Source, currentTree, new ReplacingExpressionVisitor( @@ -1159,9 +1173,11 @@ public GroupingElementReplacingExpressionVisitor( _cloningExpressionVisitor.ClonedNodesMap.Values.ToList()) .Visit(_navigationExpansionExpression.PendingSelector), _navigationExpansionExpression.CurrentParameter.Name!); + + return methodCallExpression.Update(null, new[] { navigationExpansionExpression }); } - return base.Visit(expression); + return base.VisitMethodCall(methodCallExpression); } protected override Expression VisitMember(MemberExpression memberExpression) diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index a85987df79d..2644ef6ffc0 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -645,9 +645,18 @@ when QueryableMethods.IsSumWithSelector(method): case nameof(Queryable.Select) when genericMethod == QueryableMethods.Select: - return ProcessSelect( + var result = ProcessSelect( groupBySource, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + if (result == null) + { + throw new InvalidOperationException( + CoreStrings.TranslationFailedWithDetails( + _reducingExpressionVisitor.Visit(methodCallExpression).Print(), + CoreStrings.QuerySelectContainsGrouping)); + } + + return result; case nameof(Queryable.Skip) when genericMethod == QueryableMethods.Skip: @@ -1449,9 +1458,14 @@ private GroupByNavigationExpansionExpression ProcessOrderByThenBy( return groupBySource; } - private NavigationExpansionExpression ProcessSelect(GroupByNavigationExpansionExpression groupBySource, LambdaExpression selector) + private NavigationExpansionExpression? ProcessSelect(GroupByNavigationExpansionExpression groupBySource, LambdaExpression selector) { - var selectorBody = new GroupingElementReplacingExpressionVisitor(selector.Parameters[0], groupBySource).Visit(selector.Body); + var groupingElementReplacingExpressionVisitor = new GroupingElementReplacingExpressionVisitor(selector.Parameters[0], groupBySource); + var selectorBody = groupingElementReplacingExpressionVisitor.Visit(selector.Body); + if (groupingElementReplacingExpressionVisitor.ContainsGrouping) + { + return null; + } selectorBody = Visit(selectorBody); selectorBody = new PendingSelectorExpandingExpressionVisitor(this, _extensibilityHelper, applyIncludes: true).Visit(selectorBody); selectorBody = Reduce(selectorBody); diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs index eb0cd00d230..f32ccf90274 100644 --- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs @@ -342,7 +342,7 @@ var compilerPrefixIndex if (compilerPrefixIndex != -1) { - parameterName = parameterName.Substring(compilerPrefixIndex + 1); + parameterName = parameterName[(compilerPrefixIndex + 1)..]; } parameterName diff --git a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs index 81438d949d4..40677268191 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs @@ -155,7 +155,7 @@ public override void Optional_owned_with_converter_reading_non_nullable_column() public override void Value_conversion_on_enum_collection_contains() { Assert.Contains( - CoreStrings.TranslationFailed("").Substring(47), + CoreStrings.TranslationFailed("")[47..], Assert.Throws(() => base.Value_conversion_on_enum_collection_contains()).Message); } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs index 9318643eef5..904066cd3bc 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestSqlLoggerFactory.cs @@ -66,7 +66,7 @@ public void AssertBaseline(string[] expected, bool assertOrder = true) { var methodCallLine = Environment.StackTrace.Split( new[] { _eol }, - StringSplitOptions.RemoveEmptyEntries)[3].Substring(6); + StringSplitOptions.RemoveEmptyEntries)[3][6..]; var indexMethodEnding = methodCallLine.IndexOf(')') + 1; var testName = methodCallLine.Substring(0, indexMethodEnding); diff --git a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs index b78c8ed2609..5663f7129b4 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs @@ -200,12 +200,12 @@ private string NormalizeDelimitersInRawString(string sql) private void AssertTranslationFailed(Action testCode) => Assert.Contains( - CoreStrings.TranslationFailed("").Substring(21), + CoreStrings.TranslationFailed("")[21..], Assert.Throws(testCode).Message); private void AssertTranslationFailedWithDetails(Action testCode, string details) => Assert.Contains( - CoreStrings.TranslationFailedWithDetails("", details).Substring(21), + CoreStrings.TranslationFailedWithDetails("", details)[21..], Assert.Throws(testCode).Message); protected NorthwindContext CreateContext() diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index b091920e2f9..0a376a0d207 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -2160,7 +2160,7 @@ protected virtual void ClearLog() private void AssertTranslationFailed(Action testCode) => Assert.Contains( - CoreStrings.TranslationFailed("").Substring(48), + CoreStrings.TranslationFailed("")[48..], Assert.Throws(testCode).Message); } } diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs index c157250c775..247e0a29f34 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs @@ -76,7 +76,7 @@ public void AssertBaseline(string[] expected, bool assertOrder = true) { var methodCallLine = Environment.StackTrace.Split( new[] { _eol }, - StringSplitOptions.RemoveEmptyEntries)[3].Substring(6); + StringSplitOptions.RemoveEmptyEntries)[3][6..]; var indexMethodEnding = methodCallLine.IndexOf(')') + 1; var testName = methodCallLine.Substring(0, indexMethodEnding); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index fa3441e8af6..acae1ef8e21 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -2458,6 +2459,41 @@ public virtual Task GroupBy_aggregate_followed_another_GroupBy_aggregate(bool as elementSorter: o => o.Key); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task GroupBy_aggregate_SelectMany(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => from o in ss.Set() + group o by o.CustomerID into g + let id = g.Min(x => x.OrderID) + from o in ss.Set() + where o.OrderID == id + select o))).Message; + + Assert.Contains( + CoreStrings.TranslationFailedWithDetails("", CoreStrings.QuerySelectContainsGrouping)[21..], + message); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_without_selectMany_selecting_first(bool async) + { + return AssertQuery( + async, + ss => from id in + (from o in ss.Set() + group o by o.CustomerID into g + select g.Min(x => x.OrderID)) + from o in ss.Set() + where o.OrderID == id + select o, + entryCount: 89); + } + #endregion #region GroupByAggregateChainComposition diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index 9c0a7c94146..60691242296 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -716,7 +716,7 @@ public virtual async Task Include_collection_with_client_filter(bool async) Assert.Contains( CoreStrings.TranslationFailedWithDetails( "", - CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer))).Substring(21), + CoreStrings.QueryUnableToTranslateMember(nameof(Customer.IsLondon), nameof(Customer)))[21..], (await Assert.ThrowsAsync( () => AssertQuery( async, diff --git a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs index d485833f292..32e9190ea64 100644 --- a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs @@ -1131,13 +1131,13 @@ protected void AssertGrouping( protected static async Task AssertTranslationFailed(Func query) => Assert.Contains( - CoreStrings.TranslationFailed("").Substring(48), + CoreStrings.TranslationFailed("")[48..], (await Assert.ThrowsAsync(query)) .Message); protected static async Task AssertTranslationFailedWithDetails(Func query, string details) => Assert.Contains( - CoreStrings.TranslationFailedWithDetails("", details).Substring(21), + CoreStrings.TranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)) .Message); diff --git a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs index dd1243f36af..686848bc96f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs @@ -295,7 +295,7 @@ FROM [Book] AS [b] public override void Value_conversion_on_enum_collection_contains() { Assert.Contains( - CoreStrings.TranslationFailed("").Substring(47), + CoreStrings.TranslationFailed("")[47..], Assert.Throws(() => base.Value_conversion_on_enum_collection_contains()).Message); } diff --git a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs index 5162063b089..e0fc14186b8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs @@ -1471,11 +1471,11 @@ private void AssertSql(string expected) { var methodCallLine = Environment.StackTrace.Split( new[] { Environment.NewLine }, - StringSplitOptions.RemoveEmptyEntries)[2].Substring(6); + StringSplitOptions.RemoveEmptyEntries)[2][6..]; var testName = methodCallLine.Substring(0, methodCallLine.IndexOf(')') + 1); var lineIndex = methodCallLine.LastIndexOf("line", StringComparison.Ordinal); - var lineNumber = lineIndex > 0 ? methodCallLine.Substring(lineIndex) : ""; + var lineNumber = lineIndex > 0 ? methodCallLine[lineIndex..] : ""; var currentDirectory = Directory.GetCurrentDirectory(); var logFile = currentDirectory.Substring( diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyFieldsLoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyFieldsLoadSqlServerTest.cs index 0865fa66401..7d282e5c575 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyFieldsLoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyFieldsLoadSqlServerTest.cs @@ -236,7 +236,7 @@ private void AssertSql(string expected) { var methodCallLine = Environment.StackTrace.Split( new[] { Environment.NewLine }, - StringSplitOptions.RemoveEmptyEntries)[2].Substring(6); + StringSplitOptions.RemoveEmptyEntries)[2][6..]; var indexMethodEnding = methodCallLine.IndexOf(')') + 1; var testName = methodCallLine.Substring(0, indexMethodEnding); diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs index 85e833c54c2..ddff30f2578 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs @@ -235,7 +235,7 @@ private void AssertSql(string expected) { var methodCallLine = Environment.StackTrace.Split( new[] { Environment.NewLine }, - StringSplitOptions.RemoveEmptyEntries)[2].Substring(6); + StringSplitOptions.RemoveEmptyEntries)[2][6..]; var indexMethodEnding = methodCallLine.IndexOf(')') + 1; var testName = methodCallLine.Substring(0, indexMethodEnding); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 64d2241158d..65f7fb2cc65 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -2442,6 +2442,21 @@ FROM [Orders] AS [o] GROUP BY [t].[CustomerID]"); } + public override async Task GroupBy_aggregate_without_selectMany_selecting_first(bool async) + { + await base.GroupBy_aggregate_without_selectMany_selecting_first(async); + + AssertSql( + @"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] +FROM ( + SELECT MIN([o].[OrderID]) AS [c] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] +) AS [t] +CROSS JOIN [Orders] AS [o0] +WHERE [o0].[OrderID] = [t].[c]"); + } + public override async Task GroupBy_with_grouping_key_using_Like(bool async) { await base.GroupBy_with_grouping_key_using_Like(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 36cbe28144a..0b1431eec24 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -1644,7 +1644,7 @@ CROSS JOIN ""BuiltInDataTypes"" AS ""b0"" private void AssertTranslationFailed(Action testCode) => Assert.Contains( - CoreStrings.TranslationFailed("").Substring(21), + CoreStrings.TranslationFailed("")[21..], Assert.Throws(testCode).Message); private void AssertSql(params string[] expected) diff --git a/test/EFCore.Sqlite.FunctionalTests/CustomConvertersSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/CustomConvertersSqliteTest.cs index 770a886f977..263a87e87fd 100644 --- a/test/EFCore.Sqlite.FunctionalTests/CustomConvertersSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/CustomConvertersSqliteTest.cs @@ -119,7 +119,7 @@ public override void Where_bool_gets_converted_to_equality_when_value_conversion public override void Value_conversion_on_enum_collection_contains() { Assert.Contains( - CoreStrings.TranslationFailed("").Substring(47), + CoreStrings.TranslationFailed("")[47..], Assert.Throws(() => base.Value_conversion_on_enum_collection_contains()).Message); }