Entity Framework Code First - Optional/required two-way navigation

865 views Asked by At

I have a code first model similar to the following:

public class TestContext : DbContext
{
    public DbSet<Class1> Class1s { get; set; }
    public DbSet<Class2> Class2s { get; set; }
}

public class Class1
{
    public int Class1Id { get; set; }
}

public class Class2
{
    public int Class2Id { get; set; }

    public int Class1Id { get; set; }
    public Class1 Class1 { get; set; }
}

There are zero to many Class2 objects referring to each Class1 object. Every Class2 object must have a reference to a Class1 object.

What I'd like to do is have an optional reference from Class1 to Class2 (to track one of the Class2 objects associated with the Class1 object), so I add two properties:

public class Class1
{
    public int Class1Id { get; set; }
    public int? Class2Id { get; set; }
    public Class2 Class2 { get; set; }
}

Now I get the following error when I run Add-Migration:

Unable to determine the principal end of an association between the types 'ConsoleApplication15.Class2' and 'ConsoleApplication15.Class1'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

Other StackOverflow answers indicate the solution is to specify the Foreign Key field on the dependent:

public class Class2
{
    [Key, ForeignKey("Class1")]
    public int Class2Id { get; set; }

    public int Class1Id { get; set; }
    public Class1 Class1 { get; set; }
}

My Add-Migration command now completes, but the migration is creates is as follows:

public override void Up()
{
    DropForeignKey("dbo.Class2", "Class1Id", "dbo.Class1");
    DropIndex("dbo.Class2", new[] { "Class1Id" });
    DropColumn("dbo.Class2", "Class2Id");
    RenameColumn(table: "dbo.Class2", name: "Class1Id", newName: "Class2Id");
    DropPrimaryKey("dbo.Class2");
    AddColumn("dbo.Class1", "Class2Id", c => c.Int());
    AlterColumn("dbo.Class2", "Class2Id", c => c.Int(nullable: false));
    AddPrimaryKey("dbo.Class2", "Class2Id");
    CreateIndex("dbo.Class2", "Class2Id");
    AddForeignKey("dbo.Class2", "Class2Id", "dbo.Class1", "Class1Id");
}

This doesn't look like what I want.. It's dropping the primary key on Class2 and the foreign key it adds at the end is completely wrong.

How do I describe my requirements to EF so it produces the correct model?

1

There are 1 answers

0
ocuenca On BEST ANSWER

The problem is EF take by convention you want to create an one-to-one relationship, and in case like this you need to specify which entity is the principal.

If you want to create an one to one relationship with a different PK in each entity, you can't declare FK properties in both entities. When you are configuring one-to-one relationships, Entity Framework requires that the primary key of the dependent also be the foreign key( that was you are trying to do in the last scenario).

One solution could be deleting the FK properties (Class1Id on Class2 and Class2Id on Class1) and overriding the OnModelCreating method on your context to add this configuration:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
     modelBuilder.Entity<Class2>()
                 .HasRequired(c2 => c2.Class1).
                 .WithOptional(c1=>c1.Class2);
}

If you want to work with the FK properties you could create two separate relationships as I show below:

        modelBuilder.Entity<Class2>()
                    .HasRequired(s => s.Class1)
                    .WithMany()
                    .HasForeignKey(s => s.Class1Id);

        modelBuilder.Entity<Class1>()
                    .HasOptional(s => s.Class2)
                    .WithMany()
                    .HasForeignKey(s => s.Class2Id);

I think this last solution fits more with the scenario you are trying to achieve.