1

I have the following model classes:

public class Farmer
{
    public int ID { get; set; }

    public Box Box { get; set; }
}

public class Apple
{
    public int BoxID { get; set; }

    public Box Box { get; set; }

    public int Number { get { return (int)V2.X; } set { V2 = new Vector2(value, 0); } }

    public Vector2 V2 {get;set;}

    public Farmer Owner { get; set; }
}

public class Box : IEnumerable<Apple>
{
    public int ID { get; set; }
    public ICollection<Apple> Apples { get; set; }

    public IEnumerator<Apple> GetEnumerator()
    {
        return Apples.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return Apples.GetEnumerator();
    }
}

When Box implements IEnumerable<'Apple'>, the shadow property FarmerID is added to the Apple table:

migrationBuilder.AddColumn<int>(
            name: "FarmerID",
            table: "Apples",
            nullable: true);

        migrationBuilder.CreateIndex(
            name: "IX_Apples_FarmerID",
            table: "Apples",
            column: "FarmerID");

        migrationBuilder.AddForeignKey(
            name: "FK_Apples_Farmers_FarmerID",
            table: "Apples",
            column: "FarmerID",
            principalTable: "Farmers",
            principalColumn: "ID",
            onDelete: ReferentialAction.Restrict);

When I comment out the IEnumerable implementation, the expected migration is created without the FarmerID shadow property. Why is this shadow property being generated and how can I remove it? (I've tried ignoring it, but then I end up getting FarmerID1).

I should mention this is my context class:

public class Context : DbContext
{

   public DbSet<Farmer> Farmers { get; set; }

    public DbSet<Apple> Apples { get; set; }

    public DbSet<Box> Boxes { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"...");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Apple>()
         .HasOne(a => a.Box)
         .WithMany(a => a.Apples)
         .IsRequired();

        modelBuilder.Entity<Apple>()
            .Ignore(a => a.V2)
            .Ignore(a => a.Owner)
            .HasKey(a => new { a.BoxID, a.Number });
    }
}
fomi
  • 131
  • 3
  • 9
  • *"Why is this shadow property being generated"* - does it really matter? Apparently related to property of type implementing `IEnumerable` bug, defect, shortcoming etc. In general EF Core treats classes implementing `IEnumerable<>` as collections, not entities. *"how can I remove it"* - by removing `IEnumerable` from the entity class. – Ivan Stoev Feb 25 '20 at 06:51
  • 1
    Yes it matters. It's an empty column with no purpose that takes up unnecessary space for what will be a very large table. – fomi Feb 25 '20 at 07:00
  • Of course. I meant that it doesn't really matter **why**. – Ivan Stoev Feb 25 '20 at 07:17
  • Well it does. If I know it's a bug I can report it. If it's because there's a logic problem with my code I can fix it. If I can control this behaviour with Fluent API I can work around it. – fomi Feb 25 '20 at 07:23
  • Ok, let see. What is the relationship between `Farmer` and `Box` - one-to-many or one-to-one? In other words, do you have `BoxId` column in `Farmers` table, is it nullable and is it unique? – Ivan Stoev Feb 25 '20 at 08:50
  • Interesting, in the DB they have no relationship to one another. – fomi Feb 26 '20 at 08:23
  • Hmm, then where `Farmer.Box` is coming from? Basically that's the property which in combination with `IEnumerable` is confusing EF Core and causing the problem in question. If you remove/ignore it, everything is ok. If you map it explicitly as relationship, again everything is ok. You need to investigate this further. – Ivan Stoev Feb 26 '20 at 08:51
  • 1
    Thank you @IvanStoev. With your help, I was able to debug it. I;ll post an answer now. – fomi Feb 26 '20 at 09:01

1 Answers1

0

The problem is that the ID properties of the Farmer and Box models are not getting mapped which requires EF Core to create a shadow property to Apples to map a farmer to a Box.

To solve the problem, the following can be done:

  • Rename ID to Id. EF core uses very specific naming conventions to map properties, even in constructors, so if the properties are not named according to the convention expected by EF Core, it will fail.
  • Add [Key] to ID.
  • Use Fluent API .HasKey to specify the Key.
fomi
  • 131
  • 3
  • 9
  • If someone could explain the downvote that would be great as I personally fixed it. – fomi Mar 01 '20 at 08:18