Why does EF Code First [InverseProperty] attribute fail to work when used with [ForeignKey] attribute?

Go To StackoverFlow.com

3

Using: EF 4.3.1, Visual Studio 2010, SQL CE 4.0

My understanding is that when declaring Foreign Keys with DataAnnotation in EF, it can be done either of the following ways:

Option 1-

[ForeignKey("Player1Home")]
public long? HPlayer1Id { get; set; }

public virtual Player Player1Home { get; set; }

Option 2-

public long? HPlayer1Id { get; set; }

[ForeignKey("HPlayer1Id")]
public virtual Player Player1Home { get; set; }

Problem:

When the InverseProperty DataAnnotation gets used with Option 2 an extra column gets generated in the database (Player1Home_Id) in addition to HPlayer1Id.

[Table("Matches")]
public class Match
{
    [Key]
    public long Id { get; set; }

    //-- Option 1 - THIS WORKS GREAT --//
    public long? HPlayer1Id { get; set; }
    [ForeignKey("HPlayer1Id")]
    public virtual Player Player1Home { get; set; }

    //-- Option 2 - THIS DOES NOT WORK, it generates an extra column in the database --//
    [ForeignKey("Player1Home")]
    public long? HPlayer1Id { get; set; }
    public virtual Player Player1Home { get; set; }
}

[Table("Players")]
public class Player
{
    [Key]
    public long Id { get; set; }

    [InverseProperty("Player1Home")]
    public virtual ICollection<Match> MatchesAsHome1 { get; set; }
}

Of course if I rename HPlayer1Id to Player1HomeId, then Option 2 works correctly again. But the whole purpose of the DataAnnotation is to allow explicit naming when 'Conventions' cannot automatically determine the matching property.

Removing the InverseProperty DataAnnotation on the Player class also seems to fix the issue, but unfortunately I cannot do this because my actual Match class has four Players in it, and thus I need explicit mappings.

And finally, I know I can just use Option 1, but I prefer the consistency of declaring all of my Keys (Primary and Foreign) on the Id fields rather than Foreign Keys on Navigation Properties. And technically, either way is supposed to work.

Is this just a bug in 4.3.1? In EF Code first?

Or is Mapping a ForeignKey AND an InverseProperty from two different properties to a common third property not supported?

Any information would be greatly appreciated!

Update: a second bug?

A third option should work as well (as suggested by Slauma), but causes a NullReferenceException to be thrown the first time I attempt to add an entity to the database. The database never ends up getting created, whereas Option 2 from above does not have this issue. It appears this has worked for Slauma on EF 4.1, but does not for me with EF 4.3.1. (I'm using it with SQL CE 4.0)

[Table("Matches")]
public class Match
{
    [Key]
    public long Id { get; set; }
    [ForeignKey("Player1Home")]
    public long? HPlayer1Id { get; set; }
    [InverseProperty("MatchesAsHome1")]
    public virtual Player Player1Home { get; set; }
}

[Table("Players")]
public class Player
{
    [Key]
    public long Id { get; set; }
    public virtual ICollection<Match> MatchesAsHome1 { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Match> Matches { get; set; }
    public DbSet<Player> Players { get; set; }
}

Usage:

try
{
    MyContext mc = new MyContext();
    //NullReferenceException gets thrown on the next call
    mc.Matches.Add(new Match());
    mc.SaveChanges();
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}
2012-04-04 23:26
by Scott
are you set on using the annotations - this should all ok with fluent methods configuration - annotations are a bit tricky and mixing various things could lead to problems, I usually settle with fluent - NSGaga 2012-04-05 16:27
@NSGaga, I definately prefer DataAnnotations as I personally think it's more clear for future maintainers of the software I'm writing. Though I am not set on it as I use Fluent for some things that are not avaialble with Annotations. The big thing is that Options 1 and 3 above should work, but they don't. Option 2 of course provides a workaround for now, but hopefully before the official .Net 4.5 is released, this will be fixed to work as it should - Scott 2012-04-05 17:28
ok that's a bug - I'm just offering an alternative solution - but if the question is about annotations strictly, then I won't bother: - NSGaga 2012-04-05 17:30
@NSGaga, no worries, yeah I was strictly looking at an issue with DataAnnotations. Thank you for taking a look at my question though - Scott 2012-04-05 18:38
We have confirmed that this is a bug in EF 4.3.1 and EF5-beta2. We will work on a fix and post an update when it is available. For now please use the workarounds suggested in the answer or use the fluent API for configuration - Arthur Vickers 2012-04-10 23:03


2

Same behaviour in EF 4.1.

You didn't mention the option to move the InverseProperty attribute to the other side of the relationship:

[Table("Matches")]
public class Match
{
    [Key]
    public long Id { get; set; }

    [ForeignKey("Player1Home")]
    public long? HPlayer1Id { get; set; }

    [InverseProperty("MatchesAsHome1")]
    public virtual Player Player1Home { get; set; }
}

[Table("Players")]
public class Player
{
    [Key]
    public long Id { get; set; }

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

This worked for me and didn't create the extra column.

The behaviour of your option 2 looks like a code-first bug to me.

Edit

Confirming that changing the version from EF 4.1 to EF 4.3.1 causes a NullReferenceException with the model above. The database doesn't get created.

2012-04-05 00:17
by Slauma
Using the method you suggest (which I agree should work) I get a NullReferenceException when I try to call MyContext mc = new MyContext(); mc.Matches.Add(new Match()); Whereas Option 2 from my question does not. I'll update my question with the code I'm using - Scott 2012-04-05 14:11
You got my +1 though becuase it should work and it would allow me to keep the ForeignKey on the Id field. It definitely produces a different symptom than Option 1 so it might even be a second bug - Scott 2012-04-05 14:29
@Scott: I've changed version from EF 4.1 to EF 4.3.1 and I also get the exception. The place for bug report is here: http://connect.microsoft.com/VisualStudio. But hopefully someone from EF dev team will see your question here, because they recently announced that they want to be more active with support on Stackoverflow - Slauma 2012-04-05 14:46
Slauma, Thanks for taking the time to look into this! I will report the bug(s) - Scott 2012-04-05 15:03


3

This was fixed in EF5 and I've confirmed it still behaves correctly in EF6.

You can see notes of investigation on this issue - https://entityframework.codeplex.com/workitem/138.

2013-09-23 18:46
by Rowan Miller
Thanks for the fix confirmation - Scott 2014-01-22 20:30
Ads