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"
});
});
}
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.