Mi rendo conto che la risposta è di un anno, ma la trovo incompleta. Nella mia mente, una tabella autoreferenziale viene usata per rappresentare una profondità arbitraria.
Ad esempio, si consideri la seguente struttura:
/*
* earth
* europe
* germany
* ireland
* belfast
* dublin
* south america
* brazil
* rio de janeiro
* chile
* argentina
*
*/
La risposta non risolve come eliminare la terra, o in Europa, dalla struttura di cui sopra.
In alternativa, invio il seguente codice (modifica della risposta fornita da Slauma, che ha svolto un buon lavoro tra l'altro).
Nella classe myContext, aggiungere i seguenti metodi:
public void DeleteMyEntity(MyEntity entity)
{
var target = MyEntities
.Include(x => x.Children)
.FirstOrDefault(x => x.Id == entity.Id);
RecursiveDelete(target);
SaveChanges();
}
private void RecursiveDelete(MyEntity parent)
{
if (parent.Children != null)
{
var children = MyEntities
.Include(x => x.Children)
.Where(x => x.ParentId == parent.Id);
foreach (var child in children)
{
RecursiveDelete(child);
}
}
MyEntities.Remove(parent);
}
ho popolano i dati utilizzando il codice-prima con la seguente classe:
public class TestObjectGraph
{
public MyEntity RootEntity()
{
var root = new MyEntity
{
Name = "Earth",
Children =
new List<MyEntity>
{
new MyEntity
{
Name = "Europe",
Children =
new List<MyEntity>
{
new MyEntity {Name = "Germany"},
new MyEntity
{
Name = "Ireland",
Children =
new List<MyEntity>
{
new MyEntity {Name = "Dublin"},
new MyEntity {Name = "Belfast"}
}
}
}
},
new MyEntity
{
Name = "South America",
Children =
new List<MyEntity>
{
new MyEntity
{
Name = "Brazil",
Children = new List<MyEntity>
{
new MyEntity {Name = "Rio de Janeiro"}
}
},
new MyEntity {Name = "Chile"},
new MyEntity {Name = "Argentina"}
}
}
}
};
return root;
}
}
che li risparmio al mio database con il seguente codice:
ctx.MyEntities.Add(new TestObjectGraph().RootEntity());
quindi richiamare l'elimina in questo modo:
using (var ctx = new MyContext())
{
var parent = ctx.MyEntities
.Include(e => e.Children)
.FirstOrDefault();
var deleteme = parent.Children.First();
ctx.DeleteMyEntity(deleteme);
}
che si traduce nel mio database ora avere una struttura in questo modo:
/*
* earth
* south america
* brazil
* rio de janeiro
* chile
* argentina
*
*/
in cui l'Europa e tutti i suoi figli vengono eliminati.
in quanto sopra, sto specificando il primo figlio del nodo radice, per dimostrare che usando il mio codice è possibile cancellare in modo ricorsivo un nodo e tutti i suoi figli da qualsiasi punto della gerarchia.
se si desidera verificare l'eliminazione di everyting, si può semplicemente modificare la riga in questo modo:
ctx.DeleteMyEntity(parent);
o qualsiasi nodo che si desidera nella struttura.
ovviamente, non otterrò la taglia, ma spero che il mio post aiuti qualcuno a cercare una soluzione che funzioni per entità autoreferenziali di profondità arbitraria.
Ecco il sorgente completo, che è una versione modificata del codice di Slauma dalla risposta selezionata:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace EFSelfReference
{
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public MyEntity Parent { get; set; }
public ICollection<MyEntity> Children { get; set; }
}
public class MyContext : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>()
.HasOptional(e => e.Parent)
.WithMany(e => e.Children)
.HasForeignKey(e => e.ParentId);
}
public void DeleteMyEntity(MyEntity entity)
{
var target = MyEntities
.Include(x => x.Children)
.FirstOrDefault(x => x.Id == entity.Id);
RecursiveDelete(target);
SaveChanges();
}
private void RecursiveDelete(MyEntity parent)
{
if (parent.Children != null)
{
var children = MyEntities
.Include(x => x.Children)
.Where(x => x.ParentId == parent.Id);
foreach (var child in children)
{
RecursiveDelete(child);
}
}
MyEntities.Remove(parent);
}
}
public class TestObjectGraph
{
public MyEntity RootEntity()
{
var root = new MyEntity
{
Name = "Earth",
Children =
new List<MyEntity>
{
new MyEntity
{
Name = "Europe",
Children =
new List<MyEntity>
{
new MyEntity {Name = "Germany"},
new MyEntity
{
Name = "Ireland",
Children =
new List<MyEntity>
{
new MyEntity {Name = "Dublin"},
new MyEntity {Name = "Belfast"}
}
}
}
},
new MyEntity
{
Name = "South America",
Children =
new List<MyEntity>
{
new MyEntity
{
Name = "Brazil",
Children = new List<MyEntity>
{
new MyEntity {Name = "Rio de Janeiro"}
}
},
new MyEntity {Name = "Chile"},
new MyEntity {Name = "Argentina"}
}
}
}
};
return root;
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<MyContext>(
new DropCreateDatabaseAlways<MyContext>());
using (var ctx = new MyContext())
{
ctx.Database.Initialize(false);
ctx.MyEntities.Add(new TestObjectGraph().RootEntity());
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var parent = ctx.MyEntities
.Include(e => e.Children)
.FirstOrDefault();
var deleteme = parent.Children.First();
ctx.DeleteMyEntity(deleteme);
}
Console.WriteLine("Completed....");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
}
Potete utilizzare un'eliminazione a cascata qui? Se si utilizzano le migrazioni prima del codice, è possibile impostare cascade = true quando viene creata la tabella, altrimenti potrebbe essere necessario aggiornarlo tramite un'altra migrazione o tramite il database. Penserei che EF lo gestisse se fosse in grado di effettuare una cascata. –
Buona idea. I nostri DBA non vogliono abilitare la cascata per l'intero DB, ma potremmo essere in grado di farlo solo su questo riferimento FK. È quello che stavo discutendo con un DBA. :) Anche se sto ancora cercando altri suggerimenti/idee forse nell'ambito del codice relativo a EF. – Kon
Aggiornamento: impossibile aggiungere la cascata all'eliminazione su un FK all'interno di un riferimento personale in una tabella, a causa dell'errore del ciclo. – Kon