Efcore: OwnsMany with fully-defined relationship

Created on 10 Jun 2019  路  4Comments  路  Source: dotnet/efcore

This issue is a few questions and an issue wrapped in to one, the main issue being - I don't know the difference and can't find the answer online or within the docs. You'll have to forgive me if I'm missing anything obvious, I'm fairly new to EF Core.

Let's assume I have these models:

public class Parent
{
    public Parent()
    {
    }

    public int Id { get; set; } 
    public List<Child> Children { get; set; }
}

public class Child
{
    public Child(int parentId, Parent parent)
    {
        ParentId = parentId;
        Parent = parent;
    }

    public int Id { get; set; }
    public string SomeProp { get; set; }
    public int ParentId { get; set; }
    public Parent Parent { get; set; }
}

with this entity type configuration.

   public class ParentTypeConfiguration : IEntityTypeConfiguration<Parent>
    {
        public void Configure(EntityTypeBuilder<Parent> builder)
        {
            builder
                .OwnsMany(p => p.Children, c =>      
                {
                    c.HasForeignKey(child => child.ParentId);
                    c.Property<int>("Id");
                    c.HasKey("ParentId", "Id");  
                    c.HasOne(child => child.Parent);
                }
         });
    }

Firstly a one-to-many question... What is the purpose/advantages/disadvantages of defining a complex key? From the docs "It is common to use a complex key for these type of entities incorporating the foreign key to the owner and an additional unique property that can also be in shadow state". It's completely unclear what the relationship infers and the explanation is vague at best. Specifically in an Owned one-to-many object scenario.

Secondly I want to add a navigation property to the parent, is the above example correctly defined within the context of an owned object?

Thirdly, why can't I pass the relationship object "Parent" through the constructor. This seems to throw an exception.

System.InvalidOperationException
No suitable constructor found for entity type 'Child'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'parent' in 'Child(int parentId, Parent parent)'.

I've seen some posts online about using a private constructor for EFCore and a public one for your domain model (DDD concept).

E.g.

public class Child
{
    private Child()
    {
        }

        .....
}

which seems to work, is this a valid solution?

Finally while I'm here; a bit of feedback on the documentation. I'm finding the documentation a bit lacking and the explanations of features are fairly loose with hardly any code examples. For example code to demonstrate using nested OwnsMany seems like an obvious one. With a bit more depth and complexity than just an object with an Id. Also I'm finding it a bit confusing with the naming of methods that are similar to older style methods within a CollectionOwnershipBuilder. E.g. HasOne, HasKey. It's not immediately obvious which methods are relevant to an Owned Relationship and which aren't.

Thanks in advance EF Core wizards.

Further technical details

EF Core version: 2.2.4
Database Provider: Microsoft.EntityFrameworkCore.Sqlite
Operating system: MacOS Mojave
Built for: Xamarin.Android
IDE: Visual Studio for Mac

closed-question customer-reported

Most helpful comment

Firstly a one-to-many question... What is the purpose/advantages/disadvantages of defining a complex key?

The key has to be unique, but the foreign key is not, so we can't use just the foreign key as the primary key as we do for OwnsOne. The two most straightforward solutions are:

  1. Use a single non-FK property. The contained values would need to be unique across all owners (e.g. if Parent 1 has Child 1, then Parent 2 cannot have Child 1), so the value doesn't have any inherent meaning. Since the FK is not part of the PK its values can be changed, so you could move a child from one parent to another one, however this usually goes against aggregate semantics.

  2. Use the FK and an additional property. The additional property value now only needs to be unique for a given parent (so there can be a Parent 1 with Child 1,1 and Parent 2 with Child 2,1). By making the FK part of the PK the relationship between the owner and the owned entity becomes immutable and reflects aggregate semantics better.

You can choose either option, but we show the latter one because it's both harder to configure and is better suited for the expected use of an owned type.

@divega Would you like to add something else?

Secondly I want to add a navigation property to the parent, is the above example correctly defined within the context of an owned object?

Yes, but you need to change c.HasKey("Parent", "Id"); to c.HasKey("ParentId", "Id");

Thirdly, why can't I pass the relationship object "Parent" through the constructor. This seems to throw an exception.

This is tracked in https://github.com/aspnet/EntityFrameworkCore/issues/12078. Using a private constructor is the best workaround for now.

Finally while I'm here; a bit of feedback on the documentation.

Filed https://github.com/aspnet/EntityFramework.Docs/issues/1518

All 4 comments

Firstly a one-to-many question... What is the purpose/advantages/disadvantages of defining a complex key?

The key has to be unique, but the foreign key is not, so we can't use just the foreign key as the primary key as we do for OwnsOne. The two most straightforward solutions are:

  1. Use a single non-FK property. The contained values would need to be unique across all owners (e.g. if Parent 1 has Child 1, then Parent 2 cannot have Child 1), so the value doesn't have any inherent meaning. Since the FK is not part of the PK its values can be changed, so you could move a child from one parent to another one, however this usually goes against aggregate semantics.

  2. Use the FK and an additional property. The additional property value now only needs to be unique for a given parent (so there can be a Parent 1 with Child 1,1 and Parent 2 with Child 2,1). By making the FK part of the PK the relationship between the owner and the owned entity becomes immutable and reflects aggregate semantics better.

You can choose either option, but we show the latter one because it's both harder to configure and is better suited for the expected use of an owned type.

@divega Would you like to add something else?

Secondly I want to add a navigation property to the parent, is the above example correctly defined within the context of an owned object?

Yes, but you need to change c.HasKey("Parent", "Id"); to c.HasKey("ParentId", "Id");

Thirdly, why can't I pass the relationship object "Parent" through the constructor. This seems to throw an exception.

This is tracked in https://github.com/aspnet/EntityFrameworkCore/issues/12078. Using a private constructor is the best workaround for now.

Finally while I'm here; a bit of feedback on the documentation.

Filed https://github.com/aspnet/EntityFramework.Docs/issues/1518

Yes, but you need to change c.HasKey("Parent", "Id"); to c.HasKey("ParentId", "Id");

Sorry that's a typo, I wrote the code freehand. Haven't had time to properly digest your response yet but I'll add any further questions regarding what you said then you can close the issue off. Thank you!

@divega Would you like to add something else?

@AndriySvyryd No, this is a great explanation. We should copy it into the docs. Thanks!

@divega Would you like to add something else?

@AndriySvyryd No, this is a great explanation. We should copy it into the docs. Thanks!

Agreed! To the docs!!

Was this page helpful?
0 / 5 - 0 ratings