From ba455db20068e1206c628910206726a9f0474861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurullah=20T=C3=BCrko=C4=9Flu?= Date: Mon, 19 Oct 2026 13:18:19 +0300 Subject: [PATCH] Fix precompiled queries that are the receiver of an instance method call Fixes #37961 --- .../Query/Internal/CSharpToLinqTranslator.cs | 10 ++++++- .../PrecompiledQueryRelationalTestBase.cs | 17 ++++++++++++ .../Query/PrecompiledQuerySqlServerTest.cs | 26 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs b/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs index c01789ab548..1c488d0ba6b 100644 --- a/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs +++ b/src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs @@ -102,7 +102,15 @@ public virtual Expression Translate(SyntaxNode node, SemanticModel semanticModel // Perform data flow analysis to detect all variables flowing into the query (e.g. captured variables) _dataFlowsIn.Clear(); - foreach (var flowsIn in _semanticModel.AnalyzeDataFlow(node).DataFlowsIn) + var dataFlow = _semanticModel.AnalyzeDataFlow(node); + + // Analysis fails when the query is the receiver of an instance call (dotnet/roslyn#86099) + for (var parent = node.Parent; !dataFlow.Succeeded && parent is ExpressionSyntax; parent = parent.Parent) + { + dataFlow = _semanticModel.AnalyzeDataFlow(parent); + } + + foreach (var flowsIn in dataFlow.DataFlowsIn) { _dataFlowsIn[flowsIn] = null; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs index 79cfe282b3f..4d0d85eb3ed 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/PrecompiledQueryRelationalTestBase.cs @@ -857,6 +857,23 @@ public virtual Task Terminating_with_cancellation_token() CancellationToken token = source.Token; Assert.Equal("Blog1", (await context.Blogs.Where(b => b.Id == 8).FirstOrDefaultAsync(token)).Name); Assert.Null(await context.Blogs.Where(b => b.Id == 7).FirstOrDefaultAsync(token)); +"""); + + [Fact] // #37961 + public virtual Task Terminating_with_ConfigureAwait() + => Test( + """ +CancellationTokenSource source = new CancellationTokenSource(); +CancellationToken token = source.Token; +Assert.Equal("Blog1", (await context.Blogs.Where(b => b.Id == 8).FirstOrDefaultAsync(token).ConfigureAwait(false)).Name); +"""); + + [Fact] + public virtual Task Terminating_followed_by_instance_method_call() + => Test( + """ +var id = 8; +Assert.Equal("1", context.Blogs.Count(b => b.Id == id).ToString()); """); #endregion Reducing terminating operators diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs index 6e2aa5e9bf2..b9180a2f26a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrecompiledQuerySqlServerTest.cs @@ -1659,6 +1659,32 @@ FROM [Blogs] AS [b] """); } + public override async Task Terminating_with_ConfigureAwait() + { + await base.Terminating_with_ConfigureAwait(); + + AssertSql( + """ +SELECT TOP(1) [b].[Id], [b].[Name], [b].[Json] +FROM [Blogs] AS [b] +WHERE [b].[Id] = 8 +"""); + } + + public override async Task Terminating_followed_by_instance_method_call() + { + await base.Terminating_followed_by_instance_method_call(); + + AssertSql( + """ +@id='8' + +SELECT COUNT(*) +FROM [Blogs] AS [b] +WHERE [b].[Id] = @id +"""); + } + #endregion Reducing terminating operators #region SQL expression quotability