Search code examples
c#one-to-oneef-core-3.0

How to set up multiple one-to-one relationships in Entity Framework Core?


I have two entities say Parent, and Child; each parent can have at most two child references. I have set up my entities as follows:

class Parent
{
    [Key]
    public int ParentId { get; set; }

    public int PrimaryChildId{ get; set; }
    public Child PrimaryChild { get; set; }

    public int SecondaryChildId { get; set; }
    public Child? SecondaryChild { get; set; }
    // remaining properties
}

class Child 
{
    [Key]
    public int ChildId { get; set; }

    public int ParentId { get; set; }
    public Parent Parent {get; set; }

    // remaining child properties 
}

In the DbContext.OnModelCreating I have this code:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Parent>(builder =>
    {
        builder.HasOne(p => p.PrimaryChild);
        builder.HasOne(p => p.SecondaryChild);
    });
}

This isn't enough to accomplish what I'm trying to achieve here. I get an error:

Unable to determine the relationship represented by navigation property 'Child.Parent' of type 'Parent'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'

I've tried to set up the relationship from the Child entity, but I get different errors because this makes me set up two relationships for the same property. I don't want to have two navigation properties on my child when I know only one will be used at a time as it would make for a confusing model.

I've searched the internet a bit, but I'm not having any luck finding relationships that are set up in this manner.


Solution

  • I've been trying something like this for the last few days, and after trying all sorts of Data Annotations and Fluent API nonsense, the cleanest solution I could come up with turned out to be very simple which requires neither. It only requires adding a 'private' constructor to the Parent class (or a 'protected' one if you're using Lazy Loading) into which your 'DbContext' object is injected. Just set up your 'Parent' and 'Child' classes as a normal one-to-many relationship, and with your database context now available from within the 'Parent' entity, you can make 'PrimaryChild' and 'SecondaryChild' simply return a query from the database using the Find() method. The Find() method also makes use of caching, so if you call the getter more than once, it will only make one trip to the database.

    Here is the documentation on this ability: https://learn.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services

    Note: the 'PrimaryChild' and 'SecondaryChild' properties are read-only. To modify them, set the 'PrimaryChildId' and 'SecondaryChildId' properties.

    class Parent
    {
        public Parent() { }
        private MyDbContext Context { get; set; }
        // make the following constructor 'protected' if you're using Lazy Loading
        private Parent(MyDbContext Context) { this.Context = Context; }
    
        [Key]
        public int ParentId { get; set; }
        public int PrimaryChildId { get; set; }
        public Child PrimaryChild { get { return Context.Children.Find(PrimaryChildId); } }
        public int? SecondaryChildId { get; set; }
        public Child SecondaryChild { get { return Context.Children.Find(SecondaryChildId); } }
        // remaining properties
    }
    
    class Child
    {
        [Key]
        public int ChildId { get; set; }
        public int ParentId { get; set; }
        public Parent Parent { get; set; }
        // remaining child properties 
    }