question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Owned type w/ HasMany

See original GitHub issue

First, I’m assuming Owned Types are basically trying to target https://www.martinfowler.com/bliki/DDD_Aggregate.html

I fully understand why an owned entity cannot be owned by multiple other entities (as is specified in the docs). It simply doesn’t make sense from a conceptual perspective, and very unclear technically. However, I see no reason why there can’t be relationships to the owned entity? Basically, I don’t understand why the following error message exists: The type 'Owned' cannot be configured as non-owned because an owned entity type with the same name already exists.

Below I have two scenarios. One using a DDD Aggregate approach, one without. I assume you can infer the relationships from the types, so no need for the configuration.

The one without works fine, but the one using the aggregate approach doesn’t. I don’t see any technical (or conceptual) reasons (whether one splits up Owner/Owned into two entities or keeps them in one, e.g. FlattenedOwner) shouldn’t make any difference? It’s the same amount of tables, just mapped to either 1 or 2 entities? Or am I just missing how to properly configure such scenario?

Doesn’t work:

public class Owner
{
    public int OwnerId { get; set; }
    public Owned Owned { get; set; }
}

public class Owned
{
    public string SomeProperty { get; set; }
    public List<Related> Relateds { get; set; }
}

public class Related
{
    public int RelatedId { get; set; }
    public Owned Owned { get; set; }
}

Works:

public class FlattenedOwner
{
    public int OwnerId { get; set; }
    public string SomeProperty { get; set; }
    public List<Related> Relateds { get; set; }
}

public class RelatedToFlattened
{
    public int RelatedId { get; set; }
    public FlattenedOwner FlattenedOwner { get; set; }
}

EF Core version: 5.0 Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer) Target framework: (e.g. .NET 5.0) Operating system: IDE: (e.g. Visual Studio 2019 16.8.5)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:19 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
ajcvickerscommented, Mar 29, 2021

@cjblomqvist You need to configure the nested parts of the model using the “owned” APIs. For example:

modelBuilder.Entity<Owner>(b =>
{
    b.OwnsOne(
        e => e.Owned,
        b => b.OwnsMany(
            e => e.Relateds,
            b => b.WithOwner(e => e.Owned)));
    
    b.Navigation(e => e.Owned).IsRequired();
});

This generates the following model:

Model: 
  EntityType: Owned
    Properties: 
      OwnerId (no field, int) Shadow Required PK FK AfterSave:Throw ValueGenerated.OnAdd
      SomeProperty (string)
    Navigations: 
      Relateds (List<Related>) Collection ToDependent Related Inverse: Owned
    Keys: 
      OwnerId PK
    Foreign keys: 
      Owned {'OwnerId'} -> Owner {'OwnerId'} Unique Ownership ToDependent: Owned Cascade
  EntityType: Owner
    Properties: 
      OwnerId (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Navigations: 
      Owned (Owned) ToDependent Owned
    Keys: 
      OwnerId PK
  EntityType: Related
    Properties: 
      OwnedOwnerId (no field, int) Shadow Required PK FK AfterSave:Throw
      RelatedId (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Navigations: 
      Owned (Owned) ToPrincipal Owned Inverse: Relateds
    Keys: 
      OwnedOwnerId, RelatedId PK
    Foreign keys: 
      Related {'OwnedOwnerId'} -> Owned {'OwnerId'} Ownership ToDependent: Relateds ToPrincipal: Owned Cascade
1reaction
pastushenkoycommented, May 25, 2021

I think this issue shouldn’t be closed. Martin Fowler described ideal case which is rarely met in practice. It is often needed to Navigate to owned type over an aggregate having benefits of the owned types simultaneously. My real life example is questionnaires:

public class Question
{
    public int Id { get; set; }
    public string Text { get; set; }
    public IEnumerable<PossibleAnswer> PossibleAnswers { get; set; }
}

[Owned]
public class PossibleAnswer
{
    public int Id { get; set; }
    public string Text { get; set; }
    public int OrderNumber { get; set; } // shows the order in which answers should be shown to user
}

public class Criterion
{
    public int Id { get; set; }
    public IEnumerable<CriterionAnswer> Answers { get; set; }
}

[Owned]
public class CriterionAnswer
{
    public int CriterionId { get; set; }
    public int PossibleAnswerId { get; set; }
    public PossibleAnswer PossibleAnswer { get; set; } // <- this is not possible
}

With this model we conduct surveys using questions modelled by the class above. Questions could have possible answers which are natively owned type as they have an OrderNumber field. It makes us to introduce a constraint: OrderNumber must not be duplicated inside a question, so Question becomes an Aggregate and PossibleAnswer is its owned type.

But what if we need to perform analysis on questionnaire responses. Say, we have criteria which can contain PossibleAnswerId and we need to

  1. count users, who picked this possible answers
  2. show the value from (1) with the possible answer text?

To make query PossibleAnswer’s text I will have to:

  1. store QuestionId and Question navigation property in CriterionAnswer
  2. query Criteria including Question navigation property
  3. perform O(n) scans over Question’s PossibleAnswers collections to determine PossibleAnswer’s text

Note that without QuestionId in CriterionAnswer I can get PossibleAnswer’s text only by querying all the questions in database and making client-side filtering.

If there would be and opportunity to navigate to owned types, this could be much easier by just using .PossibleAnswer.Text on CriterionAnswer.

I suggest reopening this issue.

Read more comments on GitHub >

github_iconTop Results From Across the Web

entity framework - EF Core HasMany vs OwnsMany
The entity containing an owned entity type is its owner. Owned entities are essentially a part of the owner and cannot exist without...
Read more >
Owned Entity Types - EF Core
In this article. Configuring types as owned; Implicit keys; Collections of owned types; Mapping owned types with table splitting ...
Read more >
The Fluent API HasMany Method
The HasMany method must be used in conjunction with the HasOne method to fully configure a valid relationship, adhering to the Has/With pattern...
Read more >
Eloquent: Relationships
Eloquent makes managing and working with these relationships easy, and supports a variety of common relationships: One To One; One To Many; Many...
Read more >
hasMany Relation | LoopBack Documentation
Declare a property with the factory function type HasManyRepositoryFactory<targetModel, typeof sourceModel.prototype.id> on the source repository class. call ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found