Question

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!?

Was it helpful?

Solution 2

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.

OTHER TIPS

If I understand correctly, you're trying to create a One:Many optional relationship between ClassA and ClassB. It also sounds like you always want ClassB to have an associated ClassA, which would make ClassA your principal, and ClassB your dependant.

Firstly, you'll have to change your navigation property for ClassB (in your ClassA). Since you want a many relationship, you'll need some kind of collection to store your ClassB objects.

This means you'll remove your MaybeKey in ClassA, as there isn't a single object that you're referring to, instead you now have a collection (e.g. ICollection<ClassB>). You also won't need a column in the corresponding ClassA table to refer to ClassB (because it's a one-many relation, a single column wouldn't work). There should only need to be a foreign key column in the ClassB table, referring to the ClassA to which the ClassB is related.

Your code could look like this:

public class ClassA
{
    [Key]
    public int Key { get; set; } // ClassA Key

    public virtual ICollection<ClassB> MyClassBs { get; set; } // Your optional dependants.
}

public class ClassB
{  
    [Key]
    public int Key { get; set; } // ClassB key

    public int AKey { get; set; } // Your foreign key.
    public virtual ClassA myClassA { get; set; } // Your principal.
}

The navigation properties are marked virtual so Entity Framework can override them, and provide you with lazy-loading functionality (if you require it).

To create your mapping, you'd do something along these lines (might need some alteration if I've misunderstood your goal).

modelBuilder.Entity<ClassA>()
            .HasMany(b => b.MyClassBs)     // Many ClassBs
            .WithRequired(a => a.myClassA) // Each ClassB requires a ClassA
            .HasForeignKey(b => b.AKey);   // Use Akey as a foreign key

There are a few different ways to express this (e.g. you could do it from the ClassB side).

It might be worth sticking to a slightly more conventional naming e.g ID for the primary keys and names like ClassAID for your foreign keys, as Entity framework has a number of default conventions you can make use of. It also makes it slightly easier for others to read your code (as you tend to encounter similar conventions).

Edit: Updated to include "Special" relationship.

In your ClassA you can include the following fields (which you already had in your original example, with different names):

public int SpecialBID { get; set; }
public virtual ClassB SpecialB { get; set; }

and add the following mapping:

modelBuilder.Entity<ClassA>()
            .HasOptional(x => x.SpecialB)
            .WithMany()
            .HasForeignKey(x => x.SpecialBID);

Although there is now no guarantee that your SpecialB is included in your MyClassBs collection.

You should also be able to remove the MyClassBs collection entirely from ClassA, by using a relationship mapping that doesn't require a navigation property on the other side of the relationship (in the classB object):

    modelBuilder.Entity<ClassB>()
        .HasRequired(x => x.myClassA)
        .WithMany()
        .HasForeignKey(x => x.AKey);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top