dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.66k stars 3.16k forks source link

Breaking: ArgumentOutOfRangeException in v9-RC1 #34728

Open bkoelman opened 21 hours ago

bkoelman commented 21 hours ago

File a bug

When running our test suite against EF Core 9 RC1, I see various cases where an ArgumentOutOfRangeException is thrown. These tests succeed when run against the latest versions of EF Core 8 and 6, as well as 9.0.0-preview.3.24172.4. I've verified the crash happens both with Npgsql and Sqlite, which is why I believe the issue is in EF Core and not provider-related. It appears the problem was introduced in v9.0.0-preview.7.24405.3.

Steps to reproduce

  1. Create an empty console app that targets .NET 9. Add package references to Microsoft.EntityFrameworkCore.Sqlite (9.0.0-rc.1.24451.1) and Npgsql.EntityFrameworkCore.PostgreSQL (9.0.0-rc.1).
  2. Paste the contents below in Program.cs and run.
  3. Observe the crash (see stack trace below).
  4. To verify with Postgres, start a docker container:
    docker run --pull always --rm --detach -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:latest

    and use the commented-out lines.

Include your code

Contents of Program.cs:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddDbContext<AppDbContext>(options =>
{
    //options.UseNpgsql("Host=localhost;Database=Demo;User ID=postgres;Password=postgres;Include Error Detail=true",
    //    builder => builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));

    options.UseSqlite("Data Source=SampleDb.db;Pooling=False",
        builder => builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
});

var serviceProvider = services.BuildServiceProvider();
await using var scope = serviceProvider.CreateAsyncScope();
var appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

await appDbContext.Database.EnsureDeletedAsync();
await appDbContext.Database.EnsureCreatedAsync();

var query = appDbContext.Blogs
    .AsNoTrackingWithIdentityResolution()
    .Select(
        blog => new Blog
        {
            Id = blog.Id,
            Posts = blog.Posts
                .Select(
                    blogPost => new BlogPost
                    {
                        Id = blogPost.Id,
                        Author = blogPost.Author
                    })
                .ToHashSet()
        });

_ = await query.ToArrayAsync();

public sealed class AppDbContext(DbContextOptions options) : DbContext(options)
{
    public DbSet<Blog> Blogs => Set<Blog>();
}

public sealed class Blog
{
    public long Id { 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 sealed class WebAccount
{
    public long Id { get; set; }
}

Include stack traces

Unhandled exception. System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.GetProjection(ProjectionBindingExpression projectionBindingExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonCorrectOrderOfEntitiesForChangeTrackerValidator.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonCorrectOrderOfEntitiesForChangeTrackerValidator.VisitExtension(Expression extensionExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonCorrectOrderOfEntitiesForChangeTrackerValidator.Validate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, Expression& relationalCommandResolver, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass11_0`1.<ExecuteCore>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in D:\Bart\Source\Projects\EFCore9BugRepro\EFCore9BugRepro\Program.cs:line 37
   at Program.<Main>$(String[] args) in D:\Bart\Source\Projects\EFCore9BugRepro\EFCore9BugRepro\Program.cs:line 37
   at Program.<Main>(String[] args)
roji commented 13 hours ago

Confirmed regression in 9.0, specifically for AsNoTrackingWithIdentityResolution().

@maumar interested in taking a look? I think we'd consider this for servicing.