Reputation: 281
In EF Core I have two Objects and fluent code first setup
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public PlatForm Platform { get; set; }
}
public class Platform
{
public int Id { get; set; }
public string PlatformName { get; set; }
public Customer Customer { get; set; }
}
modelBuilder.Entity<Platform>(entity =>
{
entity.ToTable("Platform").HasKey(e => e.Id);
entity.HasOne(t => t.Customer)
.WithOne(t => t.Platform)
.HasForeignKey<Customer>(t => t.Id).IsRequired();
});
modelBuilder.Entity<Customer>(entity =>
{
entity.ToTable("Customer");
entity.Property(e => e.Name).HasPrecision(50).IsRequired().HasColumnName("name");
});
When adding a platform you should select a customer to who it belongs. You can add customers without platforms but can't add platforms without customers.
When I try to add a Customer I get exception
the property is also part of a foreign key for which the principal entity in the relationship is not known.
Upvotes: 0
Views: 584
Reputation: 34653
"You can add customers without platforms but can't add platforms without customers."
This then represents a 1-to-0-or-1 relationship or essentially a restricted many-to-one, where Platform would contain the FK (CustomerId) to it's relative Customer:
Implementing with a Shadow Property for the FK:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public PlatForm Platform { get; set; }
}
public class Platform
{
public int Id { get; set; }
public string PlatformName { get; set; }
public Customer Customer { get; set; }
}
modelBuilder.Entity<Platform>(entity =>
{
entity.ToTable("Platform").HasKey(e => e.Id);
entity.HasOne(t => t.Customer)
.WithOne(t => t.Platform)
.HasForeignKey<Platform>("CustomerId").IsRequired();
});
The schema will treat this as a many-to-one where technically there isn't anything stopping you from trying to create a second platform referencing the same customer, but as far as the EF domain is concerned one customer can have 0-1 platform and a platform is required to have 1 customer. It's generally a good idea to establish the relationship as an aggregate root where-by you do not refer to Platform as top-level entity, only accessing it through the Customer. (No Platforms DbSet exposed in the DbContext)
With EF Core you can also explore setting it up as an "Owned" relationship under Customer which can either adopt the same schema or embed the platform entity fields under the Customer table. This reinforces the aggregate root relationship between the entities.
modelBuilder.Entity<Customer>(entity =>
{
entity.ToTable("Customer").HasKey(e => e.Id);
entity.OwnsOne(t => t.Platform)
.HasForeignKey<Platform>("CustomerId"); // <- not sure if this is needed
});
I'm not 100% across owned relationships, but with EF Core 3.1+ it does support separate tables for the relationship which I believe is a requirement for the relationship to be optional. (0-1 Platform) The advantage of using OwnsOne rather than HasOne
/WithOne
is that this restricts EF from allowing access to the owned entity via a DbSet
or using Set<Platform>
. With HasOne
some later coder could try and "hack around" that there isn't a Platforms DbSet by adding one, or doing something like:
var customer = context.Customers.Single(x => x.Id == customerId);
var platform = new Platform { Customer = customer };
context.Set<Platform>().Add(platform);
context.SaveChanges();
This would have all kinds of consequences since the loaded customer may have a Platform already associated, and even if not eager loaded, the reference might be loaded and replaced by this code where it probably isn't intended. With an Owned relationship this code wouldn't be allowed.
Upvotes: 1