Self referencing table with Entity Framework Core 5

Viewed 429

I have a self-referencing entity in my app. A package type with dependencies (as a list) to other package types. I am trying to design the required intermediate table with the model builder. Unfortunately I can't manage to fill the key correctly.

System.InvalidOperationException: 'Unable to track an entity of type 'PackageDependency (Dictionary<string, object>)' because its primary key property 'DependencyId' is null.'

    public class PackageType
    {
        [Key]
        public String Tag { get; set; }

        public virtual List<PackageType> Dependencies { get; set; } = new List<PackageType>();
    }

DB Context:

 protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<PackageType>()
                .HasMany(p => p.Dependencies)
                .WithMany(p => p.Dependencies)
                .UsingEntity<Dictionary<string, object>>(
                    "PackageDependency",
                    j => j
                        .HasOne<PackageType>()
                        .WithMany()
                        .HasForeignKey("PackageId")
                        .HasConstraintName("FK_PackageDependency_PackageId")
                        .OnDelete(DeleteBehavior.Cascade),
                    j => j
                        .HasOne<PackageType>()
                        .WithMany()
                        .HasForeignKey("DependencyId")
                        .HasConstraintName("FK_PackageDependency_DependencyId")
                        .OnDelete(DeleteBehavior.ClientCascade),
                    e => e.HasKey("PackageId", "DependencyId"));
        }

Does anyone have a hint for me?

Thanks a lot!

2 Answers

I'm surprised EF Core even allowed this

.HasMany(p => p.Dependencies)
.WithMany(p => p.Dependencies)

(using one and the same collection in for both sides of the relationship) without reporting an error.

Many-to-many require 2 collection navigation properties - one for each side of the relationship. So even though this is self referencing relationship, it still requires 2 separate collection navigation properties bound to the corresponding join entity FKs. e.g.

public virtual List<PackageType> ChildDependencies { get; set; } = new List<PackageType>();
public virtual List<PackageType> ParentDependencies { get; set; } = new List<PackageType>();

and

.HasMany(p => p.ChildDependencies) // -> j.PackageId
.WithMany(p => p.ParentDependencies) // -> j.DependencyId

you need two reference one is upward and other is down ward:

public class PackageType
{
    [Key]
    public String Tag { get; set; }

    public ICollection<PackageDependency> Dependents { get; set; }
    public ICollection<PackageDependency> Pendents { get; set; }
}

public class PackageDependency
{
    public string DependentTag { get; set; }
    public PackageType Dependend { get; set }
    public string PendentTag { get; set; }
    public PackageType Pendend { get; set; }
}


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<PackageDependency>()
            .HasKey(r => new { r.DependentTag, r.PendentTag });
    modelBuilder.Entity<PackageDependency>()
        .HasOne(r => r.Pendend)
        .WithMany(p => p.Dependents)
        .IsRequired()
        .OnDelete(DeleteBehavior.NoAction);
    modelBuilder.Entity<PackageDependency>()
        .HasOne(r => r.Dependend)
        .WithMany(d => d.Pendents)
        .IsRequired()
        .OnDelete(DeleteBehavior.NoAction);
}
Related