1

I'm using the Code First approach to build the database in this problem. I have the following (partial) entity:

public class Tournament {
    public int TournamentID { get; set; }
    public String Name { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int? SportID { get; set; }

    public virtual Sport Sport { get; set; }

    public virtual ICollection<Official> Officials { get; set; }
}

In the Official Entity I have this:

public class Official {
    public int OfficialID { get; set; }
    public String Surname { get; set; }
    public String FirstName { get; set; }
    public int? TournamentID { get; set; }

    public virtual Tournament Tournament { get; set; }

    public virtual ICollection<Match> Matches { get; set; }
}

Using some sample data and checking the SQL Server database, this works as I would expect it to. The tournament has a one-to-many relationship with officials.

The problem I'm having is that I would like the tournament to hold the primary key of an official as the head official. So I would add to the Tournament entity:

public int? OfficialID { get; set; } // foreign key to official table
public virtual Official HeadOfficial { get; set; } // navigation property

If I do this I get an attribute OfficialID and HeadOfficial_OfficialID in my Tournament table and I get TournamentID, Tournament_TournamentID and Tournament_TournamentID1 in my Officials table. I realise I now not only have a one-to-many relationship between Tournament and Official (seeing as a tournament can have many officials), but I also have a one-to-one relationship (seeing as a tournament can only have one head official).

How can I fix this problem?

Mekswoll
  • 1,355
  • 3
  • 15
  • 35

1 Answers1

3

You can fix the problem by giving EF a hint which navigation properties belong together. EF conventions cannot decide this anymore when you have two navigation properties in a class which refer to the same target class:

public class Tournament {
    public int TournamentID { get; set; }
    //...

    public int? OfficialID { get; set; }
    [ForeignKey("OfficialID")]
    public virtual Official HeadOfficial { get; set; }

    [InverseProperty("Tournament")] // the navigation property in Official class
    public virtual ICollection<Official> Officials { get; set; }
}

It's also possible with Fluent API if you prefer that:

modelBuilder.Entity<Tournament>()
    .HasOptional(t => t.HeadOfficial)
    .WithMany()
    .HasForeignKey(t => t.OfficialID);

modelBuilder.Entity<Tournament>()
    .HasMany(t => t.Officials)
    .WithOptional(o => o.Tournament)
    .HasForeignKey(o => o.TournamentID);
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Much obliged sir, but I don't understand the second part of the Fluent API. Entity Tournament has many officials (I understand that), but then Tournament WithOptional Tournament and HasForeignKey TournamentID? Why would a Tournament have a foreign key TournamentID? – Mekswoll Mar 27 '12 at 19:28
  • @pEkvo: The last `HasForeignKey` takes a property expression for the `Official` entity, NOT for `Tournament`, so `TournamentID` is the FK in `Official`. Basically `t` = "Tournament", `o` = "Official" in the lambda parameters. – Slauma Mar 27 '12 at 19:32
  • I used the Fluent API and it worked without problems, but I needed to configure some other tables as well which lead to me rereading your post. Do I understand it correctly that in the second modelbuilder call, a tournament has many officials which all have an optional tournament? Because in my application I demand that every officials has a TournamentID linked to it (officials can't exist on their own). So I changed WithOptional to WithRequired, but this gives me the 'cycles or multiple cascade paths' problem again. – Mekswoll Mar 28 '12 at 18:06
  • 1
    @pEkvo: Then append `.WillCascadeOnDelete(false)` at the end of the second Fluent mapping. And don't forget to change your `int? TournamentID` to `int TournamentID`. The FK property must be non-nullable for required relationships. – Slauma Mar 28 '12 at 18:11
  • It works for the creation of the database in SQL Server. But if I use the SMSS to test it out, I try to delete TournamentID = 1, then it tells me that the statement conflicted with the reference constraint TournamentID in the Officials table. If I delete a tournament then it should just delete all the officials linked to it, but it isn't doing this. – Mekswoll Mar 28 '12 at 19:31
  • Similarly, if an official is HeadOfficial for a tournament, the official can't be deleted in the Officials table, I would just want the OfficialID in the Tournament table to revert to NULL seeing as it is nullable. – Mekswoll Mar 28 '12 at 19:45
  • @pEkvo: Of course deleting in SSMS doesn't work because we've disabled cascading delete. The `OfficialID` in the tournament table IS nullable, it was your own proposal and it makes sense. Did you change it to be non-nullable? THIS would explain why you had the multiple cascading delete exception at all (I was wondering because this can only happen with 2 or more REQUIRED relationships). My proposal: Leave `Tournament.OfficialID` nullable and use the first mapping (`HasOptional`). Make `Official.TournamentID` non-nullable, use `WithRequired` in the second mapping WITHOUT `WillCascadeOnDelete`. – Slauma Mar 28 '12 at 20:12
  • that's actually what I tried, but I can't even create the database then, it cites the "Introducing FOREIGN KEY constraint 'Tournament_Officials' on table 'Officials' 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. See previous errors." error. – Mekswoll Mar 28 '12 at 20:19
  • @pEkvo: Can you create a new question for this problem? I've just tried what I've described in my last comment and it works fine without exceptions for me. This problem must be something more hidden (some other detail in your model perhaps) that we cannot solve here in the comment section. – Slauma Mar 28 '12 at 20:32
  • Yes, of course, my apologies. I'll try it out on a brand new project first, see if I can get it working there. Thanks for your help and patience. – Mekswoll Mar 28 '12 at 20:53