Ho un problema con il seguente modello:
public class ProjectPage
{
[Key]
public Guid Id { get; set; }
public Guid? HeaderId { get; set; }
public ProjectPage Header { get; set; }
public Guid? FooterId { get; set; }
public ProjectPage Footer { get; set; }
}
Sul modello di creazione ho questo:
modelBuilder.Entity<ProjectPage>().HasOptional(p => p.Header).WithMany().HasForeignKey(p => p.HeaderId).WillCascadeOnDelete(true);
modelBuilder.Entity<ProjectPage>().HasOptional(p => p.Footer).WithMany().HasForeignKey(p => p.FooterId).WillCascadeOnDelete(true);
Ma non posso aggiornare il database. Ho il seguente errore nella console di Gestione pacchetti:
L'introduzione del vincolo FOREIGN KEY "FK_dbo.ProjectPages_dbo.ProjectPages_FooterId" nella tabella "ProjectPages" può causare cicli o più percorsi a cascata. Specificare ON DELETE NO ACTION o ON UPDATE NO ACTION o modificare altri vincoli FOREIGN KEY.
Qualcuno può spiegare come rimuovere la Pagina del Progetto (che può essere Piè di Pagina o Intestazione in un'altra Pagina di Progetto)?
Questa eccezione si verifica quando si hanno più percorsi di eliminazione a cascata che potrebbero terminare il tentativo di eliminare la stessa riga nel DB. Immagina di avere ProjectPage
con la stessa Header
e il Footer
. Quando si tenta di eliminare tale ProjectPage
, a causa della configurazione delle relazioni, ci saranno due percorsi cercando di eliminare la stessa riga in DB (uno per l' Header
e l'altro per il Footer
).
È possibile evitare tali percorsi ambigui di eliminazione disabilitando l'eliminazione a cascata in una delle due relazioni utilizzando Fluent API o definendo alcune delle relazioni come facoltative (con una chiave esterna nullable, ma non è possibile configurare la relazione con l'eliminazione a cascata).
È vero, hai entrambi FK come optionals ma entrambe le relazioni sono state configurate con l'eliminazione a cascata, ecco perché EF sta lanciando quell'eccezione. La mia raccomandazione è impostare solo una relazione con eliminazione a cascata. Per quanto riguarda l'altra relazione, temo che tu debba farlo manualmente. Se, ad esempio, si sceglie Footer
da eliminare manualmente, quando si rimuove una ProjectPage
, è necessario impostare la proprietà Footer
come null
. Il problema qui è che potresti avere orfani nel tuo DB. Per evitarlo, puoi sostituire le SaveChanges
sul tuo Contesto per trovare ed eliminare orfani:
public override int SaveChanges()
{
ProjectPages
.Local
.Where(r => r.Footer== null && r.FooterId!=default(Guid)).Select(r=>r.FooterId)
.ToList()
.ForEach(id => ProjectPages.Remove(ProjectPages.Find(id)));
return base.SaveChanges();
}
Un altro modo potrebbe essere impostare il FooterId
con il default(Guid)
. A causa del tipo di proprietà PK ( Id
) è Guid
e non è Identità, è necessario impostare tale proprietà prima di aggiungere una ProjectPage
al DB. Quindi, impostare FooterId
con default(Guid)
è un altro modo per contrassegnare quell'entità che si desidera eliminare. Se scegli questa variante, il tuo metodo SaveChanges
potrebbe essere come mostrato di seguito:
public override int SaveChanges()
{
ProjectPages
.Local
.Where(r => r.Footer!= null && r.FooterId!=default(Guid)).Select(r=>r.Footer)
.ToList()
.ForEach(pp=> ProjectPages.Remove(pp));
return base.SaveChanges();
}
O:
public override int SaveChanges()
{
ProjectPages
.Local
.Where(r => r.Footer!= null && r.FooterId!=default(Guid)).Select(r=>r.Footer)
.ToList()
.ForEach(pp=> Entry(pp).State=EntityState.Deleted);
return base.SaveChanges();
}
In questo modo puoi evitare di chiamare il metodo Find
.