Skip to content

ICollection<T> properties fail #64

@wassim-k

Description

@wassim-k
using ExpressiveSharp.Mapping;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

// Bug: [ExpressiveProperty] stub using SelectMany(x => x.NavCollection) where
// NavCollection is ICollection<T> causes EF Core to throw at runtime.
//
// The generator rewrites the lambda to (x => (IEnumerable<T>)x.NavCollection),
// and EF Core cannot translate the explicit cast.
//
// Workaround: write SelectMany(x => x.NavCollection.Select(y => y)) so the
// result is already IEnumerable<T> and no cast is inserted.

using var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
using var db = new TestDbContext(connection);
db.Database.EnsureCreated();

db.Grandparents.Add(new Grandparent
{
    Parents =
    [
        new Parent
        {
            Children = [new Child { Value = 1 }, new Child { Value = 2 }]
        },
        new Parent
        {
            Children = [new Child { Value = 3 }]
        }
    ]
});
db.SaveChanges();

var results = db.Grandparents
    .Select(gp => gp.GrandChildren)
    .ToList();

foreach (var group in results)
    foreach (var child in group)
        Console.WriteLine(child.Value);

var resultsFail = db.Grandparents
    .Select(gp => gp.GrandChildrenFail)
    .ToList();

foreach (var group in resultsFail)
    foreach (var child in group)
        Console.WriteLine(child.Value);

internal class TestDbContext(SqliteConnection connection) : DbContext
{
    public DbSet<Grandparent> Grandparents => Set<Grandparent>();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite(connection).UseExpressives();
}

public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public int Value { get; set; }
}

public class Parent
{
    public int Id { get; set; }
    public int GrandparentId { get; set; }
    public virtual ICollection<Child> Children { get; set; } = [];
}

public partial class Grandparent
{
    public int Id { get; set; }
    public virtual ICollection<Parent> Parents { get; set; } = [];

    // FAILS: generator emits SelectMany(p => (IEnumerable<Child>)p.Children) — EF can't translate.
    [ExpressiveProperty("GrandChildrenFail")]
    private IEnumerable<Child> FailsExpr =>
        Parents.SelectMany(p => p.Children);

    // WORKS: .Select(c => c) inside SelectMany keeps type as IEnumerable<Child>, no cast needed.
    [ExpressiveProperty("GrandChildren")]
    private IEnumerable<Child> WorksExpr =>
        Parents.SelectMany(p => p.Children.Select(c => c));
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions