EF Code-First One-to-one relationship: Multiplicity is not valid in Role * in relationship

JensOlsen112 picture JensOlsen112 · Oct 15, 2014 · Viewed 48.4k times · Source

I'm attempting to do the following:

public class class1
{
    public int Id {get;set;}
    [ForeignKey("Class2")]
    public int Class2Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class class2
{
    public int Id { get; set;}
    [Required]
    public virtual int Class1Id {get;set;}
    [Required]
    [ForeignKey("Class1Id")]
    public Class1 Class1 {get;set;}
}

However every time I try to migrate my database I get the following error:

Class1_Class2_Target: : Multiplicity is not valid in Role 'Class2_Class1_Target' in relationship 'Class2_Class1'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.

What could be the issue here?

Answer

Gert Arnold picture Gert Arnold · Oct 17, 2014

Your model is not a 1:1 association. You can still have many Class2 objects referring to the same one Class1 object. Also, your model doesn't guarantee that a Class2 referring to a Class1 is also referred back by this Class1 object — Class1 can refer to any Class2 object.

How to configure 1:1?

The common way to guarantee (sort of) a 1:1 association in SQL is to have a table for the principal entity and one for the dependent entity where the primary key in the dependent table also is a foreign key to the principal:

1:1

(Here Class1 is the principal)

Now in a relational database, this still doesn't guarantee a 1:1 association (that's why I said 'sort of'). It's a 1:0..1 association. There can be a Class1 without a Class2. The truth is, genuine 1:1 associations are impossible in SQL, because there is no language construct that inserts two rows in different tables synchronously. 1:0..1 is the closest we get.

Fluent Mapping

To model this association in EF you can use the fluent API. Here's the standard way to do it:

class Class1Map : EntityTypeConfiguration<Class1>
{
    public Class1Map()
    {
        this.HasKey(c => c.Id);
        this.Property(c => c.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.HasRequired(c1 => c1.Class2).WithRequiredPrincipal(c2 => c2.Class1);
    }
}

And in the context:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new Class1Map());
}

And this is left of your classes:

public class Class1
{
    public int Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class Class2
{
    public int Id {get;set;}
    public virtual Class1 Class1 {get;set;}
}

There is no way to configure alternate foreign key properties in the model, because the only FK involved has to be the dependent's primary key.

The strange thing about this model is that EF doesn't stop you from creating (and saving) a class1 object without a class2. I think EF should be capable of validating this requirement before saving changes, but, apparently, it doesn't. Likewise, there are ways to delete a class2 object without deleting its class1 parent. So this HasRequired - WithRequired pair is not as stringent as it looks (and should be).

Data annotations

The only way to get this right in code is by data annotations. (Of course the database model will still not be able to enforce 1:1)

public class Class1
{
    public int Id {get;set;}
    [Required]
    public virtual Class2 Class2 {get;set;}
}

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

The [Key, ForeignKey("Class1")] annotation tells EF that Class1 is the principal entity.

Data annotations play a role in many APIs, which can be a curse, because each API chooses its own subset to implement, but here it comes in handy, because now EF not only uses them to design the data model, but also to validate entities. Now if you try to save a class1 object without a class2 you'll get a validation error.