13

I'm using entity framework code-first to create my database schema automatically, and one of my entities looks like this:

public class AssessmentsCaseStudies {
    #region Persisted fields
    [Required]
    [Key, Column(Order=0)]
    [ForeignKey("Assessment")]
    public int AssessmentId { get; set; }

    [Required]
    [Key, Column(Order=1)]
    [ForeignKey("CaseStudy")]
    public int CaseStudyId { get; set; }

    [Required]
    public int Score { get; set; }

    [ForeignKey("Follows")]
    public int? FollowsCaseStudyId { get; set; }
    #endregion

    #region Navigation properties
    public virtual Assessment Assessment { get; set; }
    public virtual CaseStudy CaseStudy { get; set; }
    public virtual CaseStudy Follows { get; set; }
    #endregion
}

When EF auto-generates my database, it generates a table with the following columns:

AssessmentId (PK, FK, int, not null)
CaseStudyId (PK, FK, int, not null)
Score (int, not null)
FollowsCaseStudyId (FK, int, null)
CaseStudy_CaseStudyId (FK, int, null)

This is all fine apart from the CaseStudy_CaseStudyId column. Why has that been generated? What is it for? How can I stop it being generated? My suspicion is that EF can no longer automatically match up CaseStudy's ICollection<AssessmentsCaseStudies> with the CaseStudyId column, so it creates its own column to link the two together for that navigation property.

Jez
  • 27,951
  • 32
  • 136
  • 233
  • I don't really understand the accepted answer to that question. I have precisely two `CaseStudy` references in `AssessmentsCaseStudies`, so I don't have a true many-to-many relationship and I don't see why I need a junction table. I do have `public virtual ICollection AssessmentsCaseStudies { get; set; }` in `CaseStudy`, though, so I want a way to say that that ICollection should relate to the `CaseStudyId` FK, not the `FollowsCaseStudyId` FK. – Jez Nov 09 '12 at 14:54
  • ok, I thought it could contain useful information. Can you try to create a trimmed down example, for example with only an `Assessment`, `CaseStudy` and `AssesmentCaseStudy` entity, each only containing an `ID` field and the `AssesmentCaseStudy` containing an `AssesmentID` and a `CaseStudyID` field? Does the problem then still happen? – CodeCaster Nov 09 '12 at 15:12
  • 1
    I don't think this question should've been closed because the accepted answer to the "dupe" question doesn't explain things well and I'd like a better answer. Please vote to reopen. – Jez Nov 09 '12 at 16:00

3 Answers3

15

Because you have two navigation properties of type CaseStudy in your AssessmentsCaseStudies entity and an AssessmentsCaseStudies collection in your CaseStudy entity EF cannot decide which of the two CaseStudy navigation properties this collection refers to. Both could be possible and both options would result in a valid but different entity model and database schema.

In such an ambiguous situation the EF convention is to create actually three relationships, i.e. your collection in CaseStudy does not refer to any of the two CaseStudy navigation properties but has a third (but not exposed and "invisible") endpoint in AssessmentsCaseStudies. This third relationship is the reason for the third foreign key your are seeing in the database - the one with the underscore. (The underscore is always a strong indication that something happend by mapping convention and not by your explicit configuration or data annotations.)

To fix the problem and to override the convention you can apply the [InverseProperty] attribute, thereby specifying the CaseStudy navigation property the AssessmentsCaseStudies collection belongs to:

[InverseProperty("AssessmentsCaseStudies")] // the collection in CaseStudy entity
public virtual CaseStudy CaseStudy { get; set; }

You can also (alternatively, you don't need both) put the attribute on the collection side:

[InverseProperty("CaseStudy")] // the CaseStudy property in AssessmentsCaseStudies entity
public virtual ICollection<AssessmentsCaseStudies> AssessmentsCaseStudies { get; set; }
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • I'm dealing with something similar - I tried your suggestion but got an exception. If you have a minute: http://stackoverflow.com/questions/19802324/why-is-this-column-getting-generated-in-ef-code-first-migrations – RobVious Nov 06 '13 at 01:52
7

For some reason, Slauma's InverseProperty attribute suggestion didn't work. What did work was me specifying the relationship between the two CaseStudy navigation properties in AssessmentsCaseStudies, and the CaseStudy entity, via the Fluent API in my database context's OnModelCreating method:

modelBuilder.Entity<AssessmentsCaseStudies>()
    .HasRequired(acs => acs.CaseStudy)
    .WithMany(cs => cs.AssessmentsCaseStudies)
    .HasForeignKey(acs => acs.CaseStudyId)
    .WillCascadeOnDelete(false);

modelBuilder.Entity<AssessmentsCaseStudies>()
    .HasOptional(acs => acs.Follows)
    .WithMany()  // No reverse navigation property
    .HasForeignKey(acs => acs.FollowsCaseStudy)
    .WillCascadeOnDelete(false);

Once that's added, the migration code that's generated when I Add-Migration no longer tries to add the CaseStudy_CaseStudyId column and I just get the FollowsCaseStudyId column added, with the appropriate foreign key relationship.

Jez
  • 27,951
  • 32
  • 136
  • 233
  • Did you get an exception with the `InverseProperty` attribute or did you still have the third FK? It should have worked. It does the same as your Fluent mapping - with one exception: The attribute doesn't disable cascading delete for the first relationship (for the second cascading delete is off by default anyway because it's an optional relationship). – Slauma Nov 11 '12 at 15:39
  • It still created the third FK. – Jez Nov 11 '12 at 17:40
0

For anyone else landing here looking for a solution, if you've tried the previous answers and are still getting an extra foreign key column, look for any properties you may have defined further down your POCO class that you did not intend to map to DB fields. Even if they contain code blocks, as with complex get accessors, Entity Framework will try to map them to the database somehow. This may result in extra foreign key columns if your properties return entities. To be safe, either decorate such properties with the [NotMapped] attribute or convert them to methods.

Tawab Wakil
  • 1,737
  • 18
  • 33