I have a simple EF Core model with two entities, Location, and Address where a Location must always have an Address.
The entities and their configuration is as follow:
public class Location {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
}
public class Address {
public virtual int AddressId { get; set; }
public string LineOne { get; set; }
public string LineTwo { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string ZipPostCode { get; set; }
public string State { get; set; }
public string LandlinePhone { get; set; }
public string MobilePhone { get; set; }
}
// Context configuration
...
protected override void OnModelCreating(ModelBuilder modelBuilder) {
...
modelBuilder.Entity<Location>().HasOne(l => l.Address).WithOne().OnDelete(DeleteBehavior.Cascade);
...
}
...
Note: The address entity does not have a reference back to the location.
With the above configuration, deleting a location should delete its address but deleting the address should result in an reference integrity error. Trying the above with a unit test using EF InMemory database gives an unexpected outcome:
[Test]
public void DeleteAddressShouldNotDeleteLocation() {
var address = new Address();
var location = new Location() {Address = address};
_context.Locations.Add(location);
_context.SaveChanges();
// Delete the address added above
_context.Addresses.Remove(_context.Addresses.Single());
_context.SaveChanges();
Assert.AreEqual(1, _context.Locations.Count()); --> ALWAYS FAILS AS THE LOCATION IS CASCADE DELETED
}
This also happens on our production server (Azure SQL) so it shouldn't be an issue with EF Core.
Is there a reason why EF is cascading the address deletion to the Location?
CodePudding user response:
With the above configuration, deleting a location should delete its address but deleting the address should result in an reference integrity error
Negative. Every relationship (except many-to-many) has principal and dependent (see Relationships - Definitions of terms). Cascade delete works from principal (referenced) to dependent (referencing). If cascade delete is disabled, then principal cannot be deleted if there are references to it.
So, who is the dependent in your case? The dependent is always the one who has the FK reference to the other, i.e. Location
, hence the behavior you are experiencing.
If you want to be other way around, remove the AddressId
FK from Location
and add LocationId
FK to Address
(or configure/use the so called "shared primary key association" by making Address
PK also a FK).
But note that there is no relational design which meets all your requirements. The closest (will meet 2 of 3) is to keep the current design with DeleteBehavior.Restrict
. This way
- Location must always have an Address - satisfied.
- deleting the address should result in an reference integrity error - satisfied
- deleting a location should delete its address - not satisfied, and cannot be, since the location is not owning the address.
CodePudding user response:
Try it with .OnDelete(DeleteBehavior.Restrict)
- counter-intuitive, but works on my project.
Think of it as "do not let address be deleted if there is a location with it".
Additional .IsRequired()
for this relationship will enforce one address - as you stated in your question (Location always have an address).