Skip to content

Ensure all SelectExpressions always have a SqlAliasManager #35570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 3, 2025

Conversation

roji
Copy link
Member

@roji roji commented Feb 2, 2025

The SQL alias refactoring work in 9.0 assumed that only mutable SelectExpressions need a SqlAliasManager, which is incorrect: pushdown may be called on immutable SelectExpressions later in the query pipeline (e.g. SqlNullabilityProcessor). This means that even our immutable SelectExpressions aren't really fully immutable (again a blurring of the mutability line which we need to get rid of).

In the meantime, this PR ensures that all SelectExpressions always have SqlAliasManager, regardless of whether they're (supposedly) mutable or not.

/cc @lauxjpn @ChrisJollyAU. As you've worked around this issue in 9 in your providers, and we've gotten only one user report, I'll hold back proposing this for backporting in a patch. But we can revisit that based on user feedback.

Fixes #35507

@roji roji requested a review from a team February 2, 2025 17:14
@ChrisJollyAU
Copy link
Contributor

Quick eyeball looks good. Will probably have a closer look during the week.

Thats fine to keep it to 10.0. It's a rather specific bug that doesn't pop up much. To be honest I actually think those writing ef core providers are the most likely to bump into this especially if they need to do some pushdowns because of any modifications for postprocessing/optimizing.

@roji
Copy link
Member Author

roji commented Feb 2, 2025

Thanks @ChrisJollyAU, a review would definitely be appreciated. Yeah, I wouldn't necessarily have even worked on this at this point if it hadn't been for the user-visible regression it causes (#35507).

@ocdi
Copy link

ocdi commented Feb 3, 2025

As the user who is reported this bug, this is a blocker for us upgrading to 9.x. Once this has been merged to main, is there a way we can get this into a 9.x build?

@roji
Copy link
Member Author

roji commented Feb 3, 2025

@ocdi the bar for patching is usually pretty high, and we haven't got any other reports of users hitting this yet - but I'll discuss with the team.

@roji roji enabled auto-merge (squash) February 3, 2025 10:05
@roji roji merged commit d58959f into dotnet:main Feb 3, 2025
7 checks passed
@roji roji deleted the SqlAliasManager branch February 3, 2025 13:12
@roji
Copy link
Member Author

roji commented Feb 4, 2025

Design decision: as this is a non-trivial and not easily quirkable fix, we'll hold off patching this for now, until we get at least one additional user report. At that point we can consider backporting this fix or a more targeted hack.

@KodeDaemon
Copy link

I am unable to use the latest version of EF9 because of this issue.

@roji
Copy link
Member Author

roji commented Apr 2, 2025

@KodeDaemon can you please post on the issue (#35507) rather than here on the PR? If enough users hit this we can take a look at backporting for 9.0. Note also that this is already fixed for 10, which will be released later this year.

@AndriiZhytariuk
Copy link

AndriiZhytariuk commented Apr 7, 2025

Hi EF core team, I encountered the same problem for EF Core 9 (.NET 9). After calling
nullablilityProcessor.Process(selectExpression, parametersValues, out canCache);
where selectExpression has a non-null SqlAliasManager you call the
protected virtual SqlExpression method VisitIn(InExpression inExpression, bool allowOptimizedExpansion, out bool nullable)
with inExpression = inExpression.Update(
item, subquery.Update(
subquery.Tables,
subquery.Predicate,
subquery.GroupBy,
subquery.Having,
projections: [subquery.Projection[0].Update(projectionExpression)],
subquery.Orderings,
subquery.Offset,
subquery.Limit));
and Update method without SqlAliasManager then in the
private SqlRemappingVisitor method PushdownIntoSubqueryInternal(bool liftOrderings = true)
you call
var subqueryAlias ​​= _sqlAliasManager.GenerateTableAlias(_tables is [{ Alias: string singleTableAlias ​​}] ? singleTableAlias ​​: "subquery");
where _sqlAliasManager is null. We get System.NullReferenceException: 'Object reference not set to an instance of an object.'

Stack traces
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.SelectExpression(string alias, System.Collections.Generic.List<Microsoft.EntityFrameworkCore.Query.SqlExpressions.TableExpressionBase> tables, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression predicate, System.Collections.Generic.List<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression> groupBy, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression having, System.Collections.Generic.List<Microsoft.EntityFrameworkCore.Query.SqlExpressions.ProjectionExpression> projections, bool distinct, System.Collections.Generic.List<Microsoft.EntityFrameworkCore.Query.SqlExpressions.OrderingExpression> orderings, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression offset, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression limit, System.Collections.Generic.ISet tags, System.Collections.Generic.IReadOnlyDictionary<string, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotation> annotations, Microsoft.EntityFrameworkCore.Query.SqlAliasManager sqlAliasManager, bool isMutable) Line 97
at //src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs(97)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.SelectExpression(string alias, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.TableExpressionBase> tables, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression predicate, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression> groupBy, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression having, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.ProjectionExpression> projections, bool distinct, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.OrderingExpression> orderings, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression offset, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression limit, System.Collections.Generic.IReadOnlySet tags, System.Collections.Generic.IReadOnlyDictionary<string, Microsoft.EntityFrameworkCore.Infrastructure.IAnnotation> annotations) Line 120
at /
/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs(120)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.Update(System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.TableExpressionBase> tables, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression predicate, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression> groupBy, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression having, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.ProjectionExpression> projections, System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Query.SqlExpressions.OrderingExpression> orderings, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression offset, Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression limit) Line 4258
at //src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs(4258)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.VisitIn(Microsoft.EntityFrameworkCore.Query.SqlExpressions.InExpression inExpression, bool allowOptimizedExpansion, out bool nullable) Line 715
at /
/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(715)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression sqlExpression, bool allowOptimizedExpansion, bool preserveColumnNullabilityInformation, out bool nullable) Line 459
at //src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(459)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable) Line 427
at /
/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(427)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression selectExpression, bool visitProjection) Line 345
at //src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(345)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression selectExpression) Line 290
at /
/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(290)
Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Process(System.Linq.Expressions.Expression queryExpression, System.Collections.Generic.IReadOnlyDictionary<string, object> parameterValues, out bool canCache) Line 85
at /_/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs(85)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Null Reference due to missing sqlAliasManager in SelectExpression
6 participants