Skip to content

Commit

Permalink
Fix to #34728 - Split query with AsNoTrackingWithIdentityResolution()…
Browse files Browse the repository at this point in the history
… throws ArgumentOutOfRangeException (#34742)

This is a regression introduced in 9.0 when trying to address a different regression (#33073)

Error is caused by a bug in JsonCorrectOrderOfEntitiesForChangeTrackerValidator, specifically it uses the initial SelectExpression to analyze structure of various shaper expressions in the query. Problem is that RelationalSplitCollectionShaperExpression has its own SelectExpression that described the collection - we should use that select expression rather than the parent.
Fix is to update SelectExpression used to process the expression when are processing RelationalSplitCollectionShaperExpression

Fixes #34728
  • Loading branch information
maumar authored Sep 24, 2024
1 parent 0545e5b commit 00b08d7
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3049,7 +3049,7 @@ private sealed class JsonCorrectOrderOfEntitiesForChangeTrackerValidator(SelectE
{
private bool _insideCollection;
private bool _insideInclude;

private SelectExpression _selectExpression = selectExpression;
private readonly
List<(IEntityType JsonEntityType, List<(IProperty? KeyProperty, int? ConstantKeyValue, int? KeyProjectionIndex)>
KeyAccessInfo)> _projectedKeyAccessInfos = [];
Expand Down Expand Up @@ -3206,8 +3206,11 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
var insideCollection = _insideCollection;
_insideCollection = true;
var oldSelectExpression = _selectExpression;
_selectExpression = splitCollectionShaperExpression.SelectExpression;
Visit(splitCollectionShaperExpression.InnerShaper);
_insideCollection = insideCollection;
_selectExpression = oldSelectExpression;

return splitCollectionShaperExpression;
}
Expand All @@ -3225,7 +3228,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
ValueBufferExpression: ProjectionBindingExpression entityProjectionBindingExpression
} entityShaperExpression)
{
var entityProjection = selectExpression.GetProjection(entityProjectionBindingExpression).GetConstantValue<object>();
var entityProjection = _selectExpression.GetProjection(entityProjectionBindingExpression).GetConstantValue<object>();

switch (entityProjection)
{
Expand Down Expand Up @@ -3264,7 +3267,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
} collectionResultExpression)
{
var collectionProjection =
selectExpression.GetProjection(collectionProjectionBindingExpression).GetConstantValue<object>();
_selectExpression.GetProjection(collectionProjectionBindingExpression).GetConstantValue<object>();

switch (collectionProjection)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,104 @@ public Test(int value)
}

#endregion

#region 34728

[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public virtual async Task NoTrackingWithIdentityResolution_split_query_basic(bool async)
{
var contextFactory = await InitializeAsync<Context34728>(
onConfiguring: o => SetQuerySplittingBehavior(o, QuerySplittingBehavior.SplitQuery));

using var context = contextFactory.CreateContext();
var query = context.Set<Context34728.Blog>()
.AsNoTrackingWithIdentityResolution()
.Select(
blog => new
{
blog.Id,
Posts = blog.Posts.Select(
blogPost => new
{
blogPost.Id,
blogPost.Author
}).ToList()
});

var test = async
? await query.ToListAsync()
: query.ToList();
}

[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public virtual async Task NoTrackingWithIdentityResolution_split_query_complex(bool async)
{
var contextFactory = await InitializeAsync<Context34728>(
onConfiguring: o => SetQuerySplittingBehavior(o, QuerySplittingBehavior.SplitQuery));

using var context = contextFactory.CreateContext();
var query = context.Set<Context34728.Blog>()
.AsNoTrackingWithIdentityResolution()
.Select(
blog => new
{
blog.Id,
Posts = blog.Posts.Select(
blogPost => new
{
blogPost.Id,
blogPost.Author
}).ToList(),
Posts2 = blog.Posts.Select(x => new
{
x.Id,
Tags = x.Tags.Select(xx => new
{
xx.Id,
xx.Name,
xx.Name.Length
}).ToList()
}).ToList()
});

var test = async
? await query.ToListAsync()
: query.ToList();
}

protected class Context34728(DbContextOptions options) : DbContext(options)
{
public DbSet<Blog> Tests { get; set; }

public sealed class Blog
{
public long Id { get; set; }
public string Name { get; set; }
public ISet<BlogPost> Posts { get; set; } = new HashSet<BlogPost>();
}

public sealed class BlogPost
{
public long Id { get; set; }
public WebAccount Author { get; set; }
public List<Tag> Tags { get; set; }
}

public sealed class WebAccount
{
public long Id { get; set; }
}

public sealed class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
}

#endregion
}

0 comments on commit 00b08d7

Please sign in to comment.