简体   繁体   中英

Entity Framework 6 FluentApi One-to-One Relationship configuration

[Table("Child", Schema = "dbo")]
public partial class Child
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int Id { get; set; }

    public int? Parent1Id { get; set; }

    public int? Parent2Id { get; set; }

    public virtual Parent Parent1 {get; set}   
    public virtual Parent Parent2 {get; set}      
}

[Table("Parent", Schema = "dbo")]
public partial class Parent
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int Id { get; set; }

    public virtual Child Child1 { get; set; }
    public virtual Child Child2 { get; set; }
  //modelBuilder below would work if I had this
  //public virtual ICollection<Child> Child1 { get; set; }

}

This is meant to be one-to-one relationship between Parent-Child. Parent1Id and Parent2Id are nullable.

I only found examples (with FluentAPI) for one-to-many relationship and if I had a collection of Child in Parent I'd have to do something like:

modelBuilder.Entity<Parent>()
                .HasMany(e => e.Child1)
                .WithOptional(e => e.Parent1)
                .HasForeignKey(e => e.Parent1Id);
modelBuilder.Entity<Parent>()
                .HasMany(e => e.Child2)
                .WithOptional(e => e.Parent2)
                .HasForeignKey(e => e.Parent2Id);

Child will have 2 FK referencig Parent. Parent has no FK referencing child.

My question is, how can I do this as one-to-one relationship using EF 6.x? I saw the HasOne() method but that's from EFCore so I'm out of options in this one.

Thanks

Note:One-to-one relationships are technically not possible in MS SQL Server. These will always be one-to-zero-or-one relationships. EF forms One-to-One relationships on entities not in the DB.

https://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx

EF6 supports naturally only one-to-one relationships using the so called shared primary key associations , where the dependent PK is also used as FK. However they are not applicable in this scenario (when you need more than one one-to-one relationships between two entities). So you need one-to-one FK based relationships.

They are supported, but with the following restrictions:

  • EF6 won't create the unique constraint/index on the FK column needed for enforcing "to 1" (or "to 0 or 1") in the database, so the from the database point of view they will be one-to-many.
  • Explicit FK properties are not supported (note the lack of HasForeignKey fluent API when configuring one-to-one relationship). You have to remove them and work only with navigation properties. The FK column name can be specified using MapKey inside Map method.

You have to accept these restrictions, otherwise you can't use EF6 with such model.

Start by removing the explicit FK properties from the model:

// remove these:
//public int? Parent1Id { get; set; }
//
//public int? Parent2Id { get; set; }

The next thing to consider is that EF relationships always consider one of the sides being principal, and the other being dependent. In one-to-many the "one" side is always the principal, and the "many" side is always the dependent. For one-to-one normally the required end is the principal, and optional end is the dependent, but when both ends are required or both end are optional (as in your case), you have to specify that by using the proper WithRequired / WithOptional method, where Principal / Dependent suffix specifies how to treat the entity being configured (the generic type argument to the Has method). This is important and is usually confusing people, because the rest of the fluent configuration inside Map applies always to the dependent entity, regardless of which entity is being used to start the configuration.

With that being said, in your sample there are two relationships, both with the Parent being the principal, Child being the dependent, and both ends being optional. Hence the fluent configuration could be

modelBuilder.Entity<Parent>()
    .HasOptional(p => p.Child1)
    .WithOptionalPrincipal(c => c.Parent1)
    .Map(c => c.MapKey("Parent1Id"));

modelBuilder.Entity<Parent>()
    .HasOptional(p => p.Child2)
    .WithOptionalPrincipal(c => c.Parent2)
    .Map(c => c.MapKey("Parent2Id"));

It could also be configured other way around:

modelBuilder.Entity<Child>()
    .HasOptional(c => c.Parent1)
    .WithOptionalDependent(p => p.Child1)
    .Map(c => c.MapKey("Parent1Id"));

modelBuilder.Entity<Child>()
    .HasOptional(c => c.Parent2)
    .WithOptionalDependent(p => p.Child2)
    .Map(c => c.MapKey("Parent2Id"));

Note: Use one or the another, not both. Usually the relationship configuration starts from the end having navigation property (since it is required for Has methods), but when both end have navigations, then it's just a matter of personal preference/taste.

The parent is the principal and the child is the dependent, thus the child requires the parent but the child is optional to the parent:

modelBuilder.Entity<Child>()
    .HasRequired(c => c.Parent1)
    .WithOptional(p => p.Child1)
    .HasForeignKey(c => c.Parent1Id);

Usually, you'd use the dependent's PK as the FK to the principal entity (which is what ensures the one-to-one relationship), but you're attempting something unusual here - you seem to want a one-to-two relationship. My opinion is to rethink your design - make it a 1:N and restrict number of dependents in application/DbContext code or derive two different types from Child so that they are different entities in the DB/ERM model, then use the standard "PK as FK" one-to-one mapping.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM