matto56
matto56

Reputation: 33

How to define nested model relationship with ValueObjects

I have a parent child relationship where the parent has a ValueObject and I cannot determine how to correctly define the relationship.

Adding a migration for the Child/Parent relationship fails with the error...

The entity type 'Address' requires a primary key to be defined.

The following is the current code structure.

public class Address
{
        [Required]
        public string BuildingNumber { get; private set; }
        
        // other address properties...
}
public class Parent
{
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; protected set; }

        [Required]
        public Address PrimaryAddress { get; private set; }
}
public class ParentContext : DbContext
{
    public ParentContext(DbContextOptions<ParentContext> options) : 
     base(options)
    {
    }

    public DbSet<Parent> Parents { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress);

        // Flatten the ValueObject fields into table
        modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress).
            Property(b => b.BuildingNumber).IsRequired().
                HasColumnName("Primary_BuildingName");
     }
}
public class Child
{
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; protected set; }

        [Required]
        public int ParentId { get; private set; }

        [ForeignKey("ParentId")]
        public Parent Parent { get; private set; }
}
public class ChildContext : DbContext
{
    public ChildContext(DbContextOptions<ChildContext> options) : base(options)
    {
    }

    public DbSet<Child> Children { get; set; }
}

Using the above code example I can run separate commands to create migrations for Parent and Child and the tables look correct.

add-migration create-parent -c parentcontext

add-migration create-child -c childcontext

Adding in the relationship to the entities and adding the final migration fails.

add-migration add-parent-child-fk -c childcontext

The problem only occurs where I have Child and Parent in a different Context.

I have tried defining the relationship different ways in both the parent and child to map the address fields so that the child 'understands' the mapping but I cannot avoid EF errors with anything I have tried.

Example Project is here

https://github.com/cimatt55/ef-parent-valueobject

Upvotes: 3

Views: 274

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205549

The main problem are the separate contexts. Value object (owned entity type) is just a consequence - if there wasn't value object, then you would have another issues.

You seem to base your design on a wrong assumption that only entity classes from publicly exposed DbSet. But that's not true. Referenced entities by navigation properties are also included, as well as referenced entities by them etc.

This is logical because EF Core context represents a database with tables and relationships. EF Core needs to know all the related entities in order to correctly support loading related data, querying (joining), cascade delete, tables, columns, primary and foreign key property/columns and their mappings etc.

This is explained in the Including & Excluding Types section of the EF Core documentation:

By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.

Adjusting their example for your ChildContext, the following types are discovered:

  • Child because it is exposed in a DbSet property on the context
  • Parent because it is discovered via the Child.Parent navigation property
  • Address because it is discovered via the Parent.PrimaryAddress navigation property

Since ChildContext has no Parent entity configuration, EF assumes everything related to Parent (and Address) to be by convention, hence the exception.

Shorty, using separate contexts containing related entities is not a good idea. The solution is to put and maintain all related entities in a single context.

Looking at the terminology used, you've probably are after DDD and bounded contexts, but these do not fit in EF Core (and generally in relational database) model.

Upvotes: 2

Related Questions