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.8k stars 3.2k forks source link

.Include with navigation collection with same name as other [NotMapped] property fails with InvalidOperationException #34242

Open DanielStout5 opened 4 months ago

DanielStout5 commented 4 months ago

File a bug

Given this class:

    public class Book : IBookCover2Haver
    {
        public List<BookCover> Covers { get; set; } = new();

        [NotMapped]
        List<BookCover2> IBookCover2Haver.Covers => Covers.Cast<BookCover2>().ToList();
    }

Calling this:

            var result = await db.Books
                .Include(x => x.Covers)
                .ToListAsync();

Throws this exception, even though the property being included is not marked with [NotMapped]:

InvalidOperationException: The expression 'x.Covers' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

Removing [NotMapped] from the second property does actually avoid this exception, but it seems to me that this is still a defect, since NotMapped should be specific to the property, not the name.

Include your code

Demo: https://github.com/DanielStout5/EfCoreBugs/tree/IncludeNotMapped

Include provider and version information

EF Core version: 7.0.11 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .Net 7

cincuranet commented 4 months ago

Minimal self-contained repro:

using var db = new MyContext();
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();
await db.Set<Book>()
    .Include(x => x.Covers)
    .LoadAsync();

public interface IBookCover2Haver
{
    List<BookCover2> Covers { get; }
}
public class Book : IBookCover2Haver
{
    public int Id { get; set; }
    public List<BookCover> Covers { get; set; } = new();

    [NotMapped]
    List<BookCover2> IBookCover2Haver.Covers => Covers.Cast<BookCover2>().ToList();
}
public class BookCover
{
    public int Id { get; set; }
}
public class BookCover2 : BookCover
{ }

class MyContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlite("Data Source=test.db");
        optionsBuilder.LogTo(Console.WriteLine);
        optionsBuilder.EnableSensitiveDataLogging();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Book>();
    }
}