Entity framework 7 in ASP.NET MVC6 multiple foreign key to the same table

asp.net-mvc entity-framework entity-framework-core

Question

Hi I have the same problem that an old post that is here, the solution offer there doesn't work for me in MVC 6 with EF7 is simple

public class Match
{
    [Key]
    public int MatchId { get; set; }

    public DateTime playday { get; set; }
    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }

    public int HomeTeamId { get; set; }
    public int GuestTeamId { get; set; }

    [ForeignKey("HomeTeamId")]
    [InverseProperty("HomeMatches")]
    public virtual Team HomeTeam { get; set; }

    [ForeignKey("GuestTeamId")]
    [InverseProperty("AwayMatches")]
    public virtual Team GuestTeam { get; set; }

}

public class Team
{
    public int TeamId { get; set; }
    public String name { get; set; }

    public virtual ICollection<Match> HomeMatches { get; set; }
    public virtual ICollection<Match> AwayMatches { get; set; }
}

this is the best way that I found, because I can add a new migration and all is ok, but when I update the database I get an error like this

Introducing FOREIGN KEY constraint 'FK_Match_Team_HomeTeamId' on table 'Match' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.

Accepted Answer

I analysed the problem in details during preparing the answer and I can suggest you two solutions of the problem.

The problem exist because of two properties in the class Match

public int HomeTeamId { get; set; }
public int GuestTeamId { get; set; }

and the foreign keys HomeTeamId and GuestTeamId which will be generated. EF7 generate the foreign keys with ON DELETE CASCADE, which can't be used for more as one foreign key. The current implementation of Entity Framework (RC1) have no annotation attribute, which you can use to change the behavior.

The first solution of the problem would be to use nullable properties like

public int? HomeTeamId { get; set; }
public int? GuestTeamId { get; set; }

or

public int HomeTeamId { get; set; }
public int? GuestTeamId { get; set; }

Maximum one property should be non nullable. As the result the problem will be solved, but one will have small disadvantage, which could be not important for some scenarios. The field in the database table for nullable property will have no NOT NULL property in the column definition.

If you do need to hold both HomeTeamId and GuestTeamId non-nullable then you can solve the problem by modifying the context class (inherited from DbContext), where the classes Match and Team be used.

You have already some context class defined line below

public class MyDBContext : DbContext
{
    DbSet<Team> Teams { get; set; }
    DbSet<Match> Matches { get; set; }
}

To solve the describe problem you can add protected OnModelCreating in the class which explicitly set

public class MyDBContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelbuilder)
    {
        base.OnModelCreating(modelbuilder);

        modelbuilder.Entity(typeof (Match))
            .HasOne(typeof (Team), "GuestTeam")
            .WithMany()
            .HasForeignKey("GuestTeamId")
            .OnDelete(DeleteBehavior.Restrict); // no ON DELETE

        modelbuilder.Entity(typeof (Match))
            .HasOne(typeof (Team), "HomeTeam")
            .WithMany()
            .HasForeignKey("GuestTeamId")
            .OnDelete(DeleteBehavior.Cascade); // set ON DELETE CASCADE
    }

    DbSet<Team> Teams { get; set; }
    DbSet<Match> Matches { get; set; }
}

You can use of cause DeleteBehavior.Restrict on both foreign keys (instead of usage of DeleteBehavior.Cascade on one). It's important to remark that the last approach allows to hold both HomeTeamId and GuestTeamId, like the corresponding fields in the database, as non-nullable.

See the documentation for additional information.


Popular Answer

If you prefer you can create on database manually this FK with cascade delete, and check why you are havign this circular cascade loop. To understand that we need the other tables or the database structure involved. Please inspect your database (with a diagram for example), draw down your connected tables, your FKs and for each study the operation you planned (cascade delete, no action, ...) and the direction of them. You fill find a loop when you will try to insert that FK in error.

The next phase is to understand if you want it or not, is an unwanted database design? Or simply you don't need this FK action? In this case you can suppress it in various levels: on model, on settings, ... se the other post or the EF guide for that.



Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why