20

I have the following data-model in Entity Framework 6.1.3:

using System.Data.Entity;

public class Student
{
    public int Id { get; set; }
    public virtual Contact Contact { get; set; }
}

public class Contact
{
    public int Id { get; set; }
    public virtual Student Student { get; set; }
}

public class MyContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder builder)
    {
        builder.Entity<Contact>()
            .HasOptional(x => x.Student)
            .WithOptionalDependent(x => x.Contact)
            .WillCascadeOnDelete(true);
    }
}

public static class Program
{
    private static void Main()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());

        using (var context = new MyContext())
            context.Database.Initialize(force: true);
    }
}

When I launch this code, I get exactly the right table structure I am aiming for:

dbo.Contacts
    Id (PK)
    Student_Id (FK, NULL, CASCADE ON DELETE)

dbo.Students
    Id (PK)

However, now I would like to add the Student_Id property to be available in the Contact entity. So I can read the Student_Id without needing to join the other table through .Student.Id navigation.

If I add the property to the Contact entity, I end up either with two columns Student_Id and Student_Id1, or I end up with an error message saying Each property name in a type must be unique..

The column is already in the database, all I need is to have it in the entity as well, why is it so much trouble? Is there a solution?

Tom Pažourek
  • 9,582
  • 8
  • 66
  • 107
  • You can prevent EF from implicitly creating the FK field by adding it to your model: public int Student_Id { get; set; } Then indicate this is the foreign key for the navigation property with an annotation or fluent. – Steve Greene Aug 31 '15 at 14:54
  • 1
    @SteveGreene: I was trying to do this, however, as I stated in the question, EF does not let me do that. It either creates two columns or it complains about the uniqueness of property names. I tried experimenting with adding the field to model, I even tried configuring the relationship with the MapKey function, no success. – Tom Pažourek Aug 31 '15 at 14:55
  • Right, you need to tell EF how to make the association with HasForeignKey or MapKey. http://patrickdesjardins.com/blog/how-to-setup-entity-framework-code-first-to-have-only-one-side-0-to-1-relationship – Steve Greene Aug 31 '15 at 15:01
  • 2
    @SteveGreene: Thanks for help. The problem with HasForeignKey is that it is only available in `WithMany` relations. And when I try to use MapKey, I end up either with two columns or with the error I mentioned. – Tom Pažourek Aug 31 '15 at 15:03

2 Answers2

18

I managed to get a response from the Entity Framework Program Manager after asking on GitHub.

Unfortunately this is a limitation of EF6. You can not have a foreign key property in a one-to-one relationship, unless it is also the primary key property. This is essentially because EF6 doesn't support alternate keys/unique indexes, so you can't enforce that a non-primary key property is unique. The fact that you can do it when the foreign key property isn't in the entity is a bit of a quirk... but obviously not something we would remove .

BTW alternate keys (and therefore this scenario) is supported in EF Core.

– Rowan Miller @ https://github.com/aspnet/EntityFramework6/issues/159#issuecomment-274889438

Tom Pažourek
  • 9,582
  • 8
  • 66
  • 107
  • EF6.1 **does** support having a foreign-key reference a non-primary unique key: https://stackoverflow.com/questions/18889218/unique-key-constraints-for-multiple-columns-in-entity-framework/25779348 - and that's been the case since 2014. so I'm curious why the linked github post is from 2017. I'm confused now. – Dai Sep 24 '20 at 09:50
9

If you want to declare the FK property in the dependent entity in an one to one relationship, I'm afraid you must use it as a PK too. EF Code First requires that PK of the dependent entity must be FK of the relationship too:

public class Contact
{
    [Key,ForeignKey("Student")]
    public int StudentId { get; set; }
    public virtual Student Student { get; set; }
}

But I think this is not what you are looking for. So, I think you have three options here:

  • You preserve your current relationship configuration.
  • Create an authentic one to one relationship.
  • Create an one to many relationship

By my experience the last one is the most adjusted to what are you trying to achieve (but that is my opinion). In this case you can work with the Fk property as you want, the only is you need to change the Contact navigation property on Student by a collection (or omit this nav. property and create an unidirectional relationship):

public class Student
{
    public int Id { get; set; }
    public virtual ICollection<Contact> Contacts { get; set; }
}

The configuration would be this way:

 builder.Entity<Contact>()
        .HasOptional(x => x.Student)
        .WithMany(x => x.Contacts)
        .HasForeignKey(x => x.StudentId)
        .WillCascadeOnDelete(true);

Update

A fourth option could be create two unidirectional relationships:

 builder.Entity<Contact>()
        .HasOptional(x => x.Student)
        .WithMany()
        .HasForeignKey(x => x.StudentId)
        .WillCascadeOnDelete(true);

 builder.Entity<Student>()
        .HasOptional(x => x.Contact)
        .WithMany()
        .HasForeignKey(x => x.ContactId)
        .WillCascadeOnDelete(true);

But this option breaks the real relation between the two tables.

ocuenca
  • 38,548
  • 11
  • 89
  • 102
  • The problem with this solution is that it forces me to have one to many relationship. I cannot have ICollection there. I do not want to change anything about my model, I don't want to change arity, it will add so many problems for me, so much overhead. I just want to read the column, which is in the database table. – Tom Pažourek Aug 31 '15 at 15:16
  • If you want to create an one to one relationship and each entities has its own PK, I'm afraid the only option is that you already have. Sorry, there is no way to map the FK column with that configuration – ocuenca Aug 31 '15 at 15:18
  • 1
    Thank you for your help. Is there a reason for that? I mean, the column is already there, I just want to read it. It would be weird if such limitation of Entity Framework would be unreasonable. – Tom Pažourek Aug 31 '15 at 15:20
  • I don't really know how EF manages that internally, the true is what are you trying to achieve is not a real one to one relationship, in an one to one rel. both entity must share the same PK. Maybe you can find more info about this subject in this [link](https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449317867/ch04s07.html) – ocuenca Aug 31 '15 at 15:25
  • This relationship is 0..1 to 0..1, which means it is not even possible to share the PK. – Tom Pažourek Aug 31 '15 at 15:28
  • Well, know seeing your last comment, maybe there are another option (which I don't like it by the way but I'm going to post it) – ocuenca Aug 31 '15 at 15:32
  • I inspected the migration script generated from this approach and it neatly adds the referential integrity and index needed in such a scenario. The downside is introducing the relationship as a collection on one of the sides of the the 1 to 0..1 relationship. – Tore Aurstad Dec 12 '18 at 13:56