I am trying to work out how to properly describe a set of database table relationships in a way that preserves both the hierarchy I have defined, as well as the cascading deletes I need. What follows is the set of tables in question, as well as the configurations where important:
public enum LocationType
{
Location,
Biosolid,
BioSite
}
public class Location
{
private Int32 _id;
public Int32 ID
{
get => _id;
set => SetProperty(ref _id, value, () => ID);
}
private LocationType _locationType;
public LocationType LocationType
{
get => _locationType;
set => SetProperty(ref _locationType, value, () => LocationType);
}
private String _name;
public String Name
{
get => _name;
set => SetProperty(ref _name, value, () => Name);
}
}
public class LocationConfiguration : IEntityTypeConfiguration<Location>
{
public void Configure(EntityTypeBuilder<Location> builder)
{
builder.ToTable("Location");
builder.HasDiscriminator(x => x.LocationType)
.HasValue(LocationType.Location)
.HasValue<BioSite>(LocationType.BioSite)
.HasValue<Biosolid>(LocationType.Biosolid);
builder.Property(x => x.LocationType).IsRequired();
builder.Property(x => x.Name).HasMaxLength(30).IsRequired();
builder.HasIndex(x => x.Name).IsUnique();
}
}
public class BioSite : Location
{
private BiosolidsSiteType _biosolidsSiteType;
public BiosolidsSiteType BiosolidsSiteType
{
get => _biosolidsSiteType;
set => SetProperty(ref _biosolidsSiteType, value, () => BiosolidsSiteType);
}
private Double? _siteArea;
public Double? SiteArea
{
get => _siteArea;
set => SetProperty(ref _siteArea, value, () => SiteArea);
}
}
public class Biosolid : Location
{
private BiosolidsType _biosolidsType;
public BiosolidsType BiosolidsType
{
get => _biosolidsType;
set => SetProperty(ref _biosolidsType, value, () => BiosolidsType);
}
}
public class BioApplication
{
private Int32 _id;
public Int32 ID
{
get => _id;
set => SetProperty(ref _id, value, () => ID);
}
private DateTime _dateTime;
public DateTime DateTime
{
get => _dateTime;
set => SetProperty(ref _dateTime, value, () => DateTime);
}
public Int32 BioSiteID { get; set; }
public BioSite BioSite { get; set; }
public Int32 BiosolidID { get; set; }
public Biosolid Biosolid { get; set; }
}
public class BioApplicationConfiguration : IEntityTypeConfiguration<BioApplication>
{
public void Configure(EntityTypeBuilder<BioApplication> builder)
{
builder.ToTable("BioApplication");
builder.Property(x => x.DateTime).IsRequired();
builder.Property(x => x.BioSiteID).IsRequired();
builder.Property(x => x.BiosolidID).IsRequired();
builder.HasIndex(x => x.BioSiteID).IsUnique(false);
builder.HasIndex(x => x.BiosolidID).IsUnique(false);
builder.HasOne(x => x.BioSite).WithMany().HasForeignKey(y => y.BioSiteID).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(x => x.Biosolid).WithMany().HasForeignKey(y => y.BiosolidID).OnDelete(DeleteBehavior.Cascade);
}
}
I am using Entity Framework Core 5.0.2, and targeting .NET (Core) 5.0 while writing in WPF.
The problem occurs at runtime as the database is being created, and I get this error message:
Microsoft.Data.SqlClient.SqlException: 'Introducing FOREIGN KEY constraint 'FK_BioApplication_Location_BiosolidID' on table 'BioApplication' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Having read up on this, I understand what it wants. The part I don't understand is why it does not seem to respect the Discriminator I defined and know that only one should be triggering the deletion. If it is simply because all it cares about is that they both resolve to Location I can understand that. Although that makes me question the usefulness of TPH, but I suppose that's another matter.
So the question is: can I configure my tables or fluent API configs to get everything I want out of this?