asp.net-coreentity-framework-coreef-fluent-apiasp.net-core-configurationowned-types

Seeding Data - Owned Type without Id


I have an Entity describing an organization, including its postal address. The Address is stored in a property (PostalAddress) and all properties of Organization is stored and flattened in the same database table. The configuration compiles and the migration is applied without any problems - if I don't seed data. I have tested standard CRUD in Razor Pages - no problem.

When adding a Seed in OnModelCreating I get an error when compiling.

The seed entity for entity type 'Organization.PostalAddress#PostalAddress' cannot be added because no value was provided for the required property 'OrganizationId'.

The message confuses me, as neither Organization, nor PostalAddress have an OrganizationId property. Neither does a shadow property exist in the db. Any ideas of what causes the problem?

public abstract class BaseEntity<TEntity>
{
    [Key] public virtual TEntity Id { get; set; }
}

public class MyOrganization : BaseEntity<long>
{
    public string Name { get; set; }                    // Name of the Organization
    public PostalAddress PostalAddress { get; set; }    // Postal address of the Organization
    public string Email { get; set; }                   // Email of the Organization
}

public class PostalAddress
{
    public string StreetAddress1 { get; set; }  // Address line 1
    public string ZipCode_City { get; set; }    // Zip code
    public string Country { get; set; }         // Country
}
public void Configure(EntityTypeBuilder<Organization> builder)
{
    builder
        .ToTable("Organizations")
        .HasKey(k => k.Id);

    // Configure PostalAddress owned entity
    builder
        .OwnsOne(p => p.PostalAddress, postaladdress =>
        {
            postaladdress
                .Property(p => p.StreetAddress1)
                .HasColumnName("StreetAddress1")
                .HasColumnType("nvarchar(max)")
                .IsRequired(false);
            postaladdress
                .Property(p => p.ZipCode_City)
                .HasColumnName("ZipCode_City")
                .HasColumnType("nvarchar(max)")
                .IsRequired(false);
            postaladdress
                .Property(p => p.Country)
                .HasColumnName("Country")
                .HasColumnType("nvarchar(max)")
                .IsRequired(false);
        });
}

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

    builder
        .AddDBConfigurations();

    // Seed data
    builder
        .Entity<Organization>(b =>
        {
            b.HasData(new Organization 
            {
                Id = 1,
                Name = "Test Organization",
                Email = "nobody@nowhere.com"
            });
            b.OwnsOne(e => e.PostalAddress)
                .HasData(new 
                { 
                    StreetAddress1 = "1600 Pennsylvania Avenue NW", ZipCode_City = "Washington, D.C. 20500", Country = "USA"
                });
        }); 
}

Solution

  • The exception message is enigmatic, but helpful. When you add OrganizationId to the seeding code for PostalAddress it works.

    modelBuilder
        .Entity<Organization>(b =>
        {
            b.HasData(new Organization
            {
                Id = 1, // Here int is OK.
                Name = "Test Organization",
                Email = "nobody@nowhere.com"
            });
            b.OwnsOne(e => e.PostalAddress)
                .HasData(new
                {
                    OrganizationId = 1L, // OrganizationId, not Id, and the type must match.
                    StreetAddress1 = "1600 Pennsylvania Avenue NW",
                    ZipCode_City = "Washington, D.C. 20500",
                    Country = "USA"
                });
        });
    

    Probably some undocumented convention is involved here. Which makes sense: the owned type can be added to more entity classes and EF needs to know which one it is. One would think that Id should be enough, after all, the type is clearly added to b, the Organization. I think some inner code leaks into the public API here and one day this glitch may be fixed.

    Note that the type of the owner's Id must match exactly, while the seeding for the type itself accepts a value that has an implicit conversion.