Search code examples
entity-frameworkforeign-keysrelationshipsdata-modeling

How do I get a combined Optional relationship and One to Many relationship?


I'm confused about how to model my relationship in Entity Framework.

I want ClassA to optionally point to a ClassB. That sounds kind of like an optional one-to-one relationship.

I also want ClassB to always point to the ClassA that points to it. I also want to permit multiple ClassB's to refer to the same ClassA. That sounds like a one-to-many relationship.

Is this best modelled as one relationship, or is this really two completely separate relationships?

If it's two relationships can they share the single property 'ClassAKey' which appears on ClassB to be used to refer to ClassA?

In my problem it's clear in my head what I want my OM to be:

ClassA
{
    [Key]
    int Key { get; set; }
    ClassB Maybe { get; set; }
    int MaybeKey { get; set; }
}

ClassB
{
    [Key]
    int Key { get; set; }
    ClassA Always { get; set; }
    int AlwaysKey { get; set; }
}

It's also clear in my head what that should look like in the DB: there should be just a ClassAKey column for ClassB and a ClassBKey column for ClassA so that each can refer to each other, and foreign key relationships on these columns.

But... I'm not clear on what the way to model this in EF is. In fact it seems like I must be doing something wrong! If I just start off with the code above, I get an error.

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

If I try to configure it like this,

modelBuilder.Entity<ClassA>()
            .HasOptional(a => a.Maybe)
            .WithRequired(b => b.Always);

I for some reason get what looks like the wrong designer code generated where ClassB has the wrong foreign key property

.ForeignKey("dbo.ClassAs", t => t.Key)

it should be t.AlwaysKey, right? What's going on!?


Solution

  • In the end this is what I believe should have worked, but I didn't get it working (it may be a bug in Entity Framework 5):

    modelBuilder.Entity<ClassB>()
        .HasRequired(a => a.Always)
        .WithMany()
        .HasForeignKey(a => a.AlwaysKey);
    
    modelBuilder.Entity<ClassB>()
        .HasOptional(b => b.Maybe);
    

    And this is what actually worked [commenting out the last two lines]:

    modelBuilder.Entity<ClassB>()
        .HasRequired(a => a.Always)
        .WithMany()
        .HasForeignKey(a => a.AlwaysKey);
    
    //modelBuilder.Entity<ClassB>()
    //    .HasOptional(b => b.Maybe);
    

    Also I changed MaybeKey to be nullable int.

     int? MaybeKey { get; set; }
    

    The second, optional relationship works by convention, instead of explicit configuration.