Sto lavorando a un progetto ASP.NET MVC 6 con Entity-Framework Core (versione "EntityFramework.Core": "7.0.0-rc1-final"
) supportato da un DB Express di SQL Server 2012.
Devo modellare una relazione molti-a-molti tra un'entità Person
e un'entità Address
. Come da questa guida, l'ho modellato con un'entità tabella join PersonAddress
, perché in questo modo posso memorizzare alcune informazioni extra.
Il mio obiettivo è configurare il mio sistema in questo modo:
Person
viene eliminata, è necessario eliminare tutte le istanze PersonAddress
correlate. Anche tutte le istanze di Address
cui fanno riferimento devono essere eliminate, solo se non sono correlate ad altre istanze di PersonAddress
. PersonAddress
viene eliminata, l'istanza di Address
fa riferimento deve essere eliminata solo se non è correlata ad altre istanze di PersonAddress
. Tutte le istanze di Person
devono vivere. Address
viene eliminata, è necessario eliminare tutte le istanze PersonAddress
correlate. Tutte le istanze di Person
devono vivere. Penso che gran parte del lavoro debba essere svolto nel rapporto molti-a-molti tra Person
e Address
, ma mi aspetto di scrivere anche un po 'di logica. Lascerò questa parte fuori da questa domanda. Quello che mi interessa è come configurare la mia relazione molti-a-molti.
Ecco la situazione attuale.
Questa è l'entità Person
. Si noti che questa entità ha relazioni uno-a-molti con altre entità secondarie.
public class Person
{
public int Id {get; set; } //PK
public virtual ICollection<Telephone> Telephones { get; set; } //navigation property
public virtual ICollection<PersonAddress> Addresses { get; set; } //navigation property for the many-to-many relationship
}
Questa è l'entità Address
.
public class Address
{
public int Id { get; set; } //PK
public int CityId { get; set; } //FK
public City City { get; set; } //navigation property
public virtual ICollection<PersonAddress> People { get; set; } //navigation property
}
Questa è l'entità PersonAddress
.
public class PersonAddress
{
//PK: PersonId + AddressId
public int PersonId { get; set; } //FK
public Person Person {get; set; } //navigation property
public int AddressId { get; set; } //FK
public Address Address {get; set; } //navigation property
//other info removed for simplicity
}
Questa è l'entità DatabaseContext
, in cui sono descritte tutte le relazioni.
public class DataBaseContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//All the telephones must be deleteded alongside a Person.
//Deleting a telephone must not delete the person it refers to.
builder.Entity<Person>()
.HasMany(p => p.Telephones)
.WithOne(p => p.Person);
//I don't want to delete the City when I delete an Address
builder.Entity<Address>()
.HasOne(p => p.City)
.WithMany(p => p.Addresses)
.IsRequired().OnDelete(Microsoft.Data.Entity.Metadata.DeleteBehavior.Restrict);
//PK for the join entity
builder.Entity<PersonAddress>()
.HasKey(x => new { x.AddressId, x.PersonId });
builder.Entity<PersonAddress>()
.HasOne(p => p.Person)
.WithMany(p => p.Addresses)
.IsRequired();
builder.Entity<PersonAddress>()
.HasOne(p => p.Address)
.WithMany(p => p.People)
.IsRequired();
}
}
Entrambe le entità Telephone
e City
sono state rimosse per motivi di semplicità.
Questo è il codice per rimuovere una Person
.
Person person = await _context.People.SingleAsync(m => m.Id == id);
try
{
_context.People.Remove(person);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
}
Per quanto riguarda le mie letture, evitare .Include()
consentirà al DB di occuparsi delle eventuali eliminazioni di CASCADE
. Mi dispiace ma non ricordo la domanda SO in cui questo concetto è stato chiarito.
Se eseguo questo codice, posso eseguire il seeding del DB utilizzando questa soluzione alternativa . Quando desidero testare l'eliminazione di un'entità Person
con il codice sopra riportato, ottengo questa eccezione:
The DELETE statement conflicted with the REFERENCE constraint "FK_PersonAddress_Person_PersonId". The conflict occurred in database "<dbName>", table "<dbo>.PersonAddress", column 'PersonId'.
The statement has been terminated.
Ho testato diverse configurazioni delle relazioni nel metodo DatabaseContext.OnModelCreating
senza alcuna fortuna.
Infine, ecco la mia domanda . Come devo configurare la mia relazione molti-a-molti al fine di eliminare correttamente una Person
e le sue entità correlate dalla mia applicazione, secondo l' obiettivo precedentemente descritto?
Grazie a tutti.
Innanzitutto vedo che hai impostato la relazione Città e Indirizzo con DeleteBehavior.Restrict
e dici: " // Non voglio eliminare la città quando elimino un indirizzo ".
Ma non è necessario Limitare qui, perché anche con DeleteBehavior.Cascade
City non verrà eliminato. Stai guardando dal lato sbagliato. Ciò che Cascade
fa qui è quando viene cancellata una città anche tutti gli indirizzi che appartengono ad essa vengono cancellati. E questo comportamento è logico.
In secondo luogo, la tua relazione molti-a-molti va bene. Quando si elimina la persona, i suoi collegamenti dalla tabella PersonAddress verranno automaticamente eliminati a causa di Cascade. E se vuoi anche eliminare gli indirizzi che sono stati collegati solo a quella persona dovrai farlo manualmente. In realtà è necessario eliminare quegli indirizzi prima di eliminare la persona è l'ordine per sapere cosa eliminare.
Quindi la logica dovrebbe seguire:
1. Interrogare attraverso tutti i record di PersonAddress dove PersonId = person.Id
;
2. Di quelli prendono solo quelli che hanno singola occorrenza di AddressId nella tabella PersonAddress e li eliminano dalla tabella Persona.
3. Ora cancella la persona.
Puoi farlo direttamente nel codice, o se vuoi che il database lo faccia per te, il trigger può essere creato per il passo 2 con la funzione: quando la riga da PersonAddress sta per essere cancellata controlla se non ci sono più righe con lo stesso indirizzo in quel Tabella PersonAddress in tal caso eliminarla dalla tabella Indirizzo.
Maggiori informazioni qui:
Come eliminare in cascata molti su molti tavoli
Come faccio a eliminare da più tabelle utilizzando INNER JOIN nel server SQL