Can I define table relationships in this situation to get cascade deletes to flow even though the underlying data types overlap?

Viewed 174

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?

0 Answers
Related