Skip to content

ComplexCollection Entity splitting support #37982

@sebastianlausmann

Description

@sebastianlausmann

Bug description

When using a ComplexCollection (JSON) mapping on an entity that is split across multiple tables using SplitToTable, queries that include the complex collection fail with:

System.InvalidOperationException: Sequence contains more than one element

Additional issues observed:

  • Despite using Table splitting, the framework attempts to write data for the "Tags" collection to both tables
  • The [Column("tags")] attribute is ignored and the C# property name "Tags" is used instead for column mapping

Your code

#:package Microsoft.EntityFrameworkCore@10.0.5
#:package Microsoft.EntityFrameworkCore.Sqlite@10.0.5
#:property PublishAot=false

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations.Schema;

await using var conn = new SqliteConnection("Data Source=:memory:");
conn.Open();

var options = new DbContextOptionsBuilder<AppDbContext>()
    .UseSqlite(conn)
    .EnableDetailedErrors()
    .LogTo(Console.WriteLine, LogLevel.Information)
    .Options;

await using (var ctx = new AppDbContext(options))
{
    ctx.Database.EnsureCreated();

    var post = new Post
    {
        Title = "Hello",
        IsFeatured = true,
        Tags =
        [
            new Tag { Name = "a" },
            new Tag { Name = "b" }
        ]
    };
    ctx.Posts.Add(post);
    await ctx.SaveChangesAsync();
}

await using (var ctx = new AppDbContext(options))
{
    var data = await ctx.Posts
        .AsNoTracking()
        .Where(p => p.Tags.Any(t => t.Name.ToLower().Contains("a")))
        .ToListAsync();

    Console.WriteLine($"Posts count: {data.Count}");
}

[Table("posts")]
class Post
{
    [Column("id")]
    public int Id { get; set; }

    [Column("title")]
    public string? Title { get; set; }

    [Column("is_featured")]
    public bool IsFeatured { get; set; }

    [Column("tags")]
    public List<Tag> Tags { get; set; } = [];
}

class Tag
{
    public string Name { get; set; } = string.Empty;
}

class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Post> Posts => Set<Post>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasKey(p => p.Id);
        modelBuilder.Entity<Post>().ComplexCollection(p => p.Tags, c => c.ToJson());

        modelBuilder.Entity<Post>().SplitToTable("posts_extra", tb =>
        {
            tb.Property(p => p.IsFeatured);
        });
    }
}

Stack traces

Unhandled exception. System.InvalidOperationException: Sequence contains more than one element
   at System.Linq.ThrowHelper.ThrowMoreThanOneElementException()
   at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)
   at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.GenerateJsonReader(Int32 jsonColumnIndex, ITypeBase structuralType)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessTopLevelComplexJsonProperties(StructuralTypeShaperExpression shaper, ParameterExpression instanceVariable, List`1 expressions)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.AddStructuralTypeInitialization(StructuralTypeShaperExpression shaper, ParameterExpression instanceVariable, List`1 variables, List`1 expressions)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.StructuralTypeMaterializerInjector.MaterializeEntity(StructuralTypeShaperExpression shaper, ParameterExpression materializationContextVariable, ParameterExpression concreteEntityTypeVariable, ParameterExpression instanceVariable, ParameterExpression entryVariable)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.StructuralTypeMaterializerInjector.ProcessStructuralTypeShaper(StructuralTypeShaperExpression shaper)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.StructuralTypeMaterializerInjector.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.StructuralTypeMaterializerInjector.Inject(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.InjectStructuralTypeMaterializers(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   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 Program.<Main>$(String[] args)
   at Program.<Main>$(String[] args)
   at Program.<Main>$(String[] args)
   at Program.<Main>(String[] args)

Verbose output


EF Core version

10.0.5

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL, Microsoft.EntityFrameworkCore.Sqlite

Target framework

.NET 10.0

Operating system

No response

IDE

No response

Metadata

Metadata

Assignees

No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions