Non è stato possibile modificare la relazione perché una o più proprietà della chiave esterna non sono annullabili


192

Ricevo questo errore quando GetById () su un'entità e quindi imposto la raccolta di entità figlio sul mio nuovo elenco che proviene dalla vista MVC.

Operazione non riuscita: non è stato possibile modificare la relazione perché una o più proprietà della chiave esterna non sono annullabili. Quando viene apportata una modifica a una relazione, la proprietà della chiave esterna correlata viene impostata su un valore null. Se la chiave esterna non supporta valori null, è necessario definire una nuova relazione, assegnare alla proprietà della chiave esterna un altro valore non nullo oppure eliminare l'oggetto non correlato.

Non capisco bene questa linea:

Non è stato possibile modificare la relazione perché una o più proprietà della chiave esterna non sono annullabili.

Perché dovrei cambiare la relazione tra 2 entità? Dovrebbe rimanere lo stesso per tutta la durata dell'intera applicazione.

Il codice su cui si verifica l'eccezione è la semplice assegnazione di classi figlio modificate in una raccolta alla classe genitore esistente. Si spera che ciò soddisfi la necessità di rimuovere le classi secondarie, aggiungerne di nuove e modificarle. Avrei pensato che Entity Framework gestisse questo.

Le righe di codice possono essere distillate per:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

Ho trovato la mia risposta acquista utilizzando la soluzione n. 2 nell'articolo di seguito, in pratica ho creato una chiave primaria nella tabella figlio per il riferimento alla tabella padre (quindi ha 2 chiavi primarie (la chiave esterna per la tabella padre e l'ID per la tabella figlio) .c
sharpcorner.com

@jaffa, ho trovato la mia risposta qui stackoverflow.com/questions/22858491/…
antonio

Risposte:


159

È necessario eliminare thisParent.ChildItemsmanualmente i vecchi elementi figlio uno alla volta. Entity Framework non lo fa per te. Finalmente non può decidere cosa vuoi fare con gli oggetti figlio vecchi - se vuoi buttarli via o se vuoi tenerli e assegnarli ad altre entità padre. Devi dire a Entity Framework la tua decisione. Ma una di queste due decisioni DEVI prendere poiché le entità figlio non possono vivere da sole senza riferimento a nessun genitore nel database (a causa del vincolo di chiave esterna). Questo è fondamentalmente ciò che dice l'eccezione.

modificare

Cosa farei se gli elementi figlio potessero essere aggiunti, aggiornati ed eliminati:

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

Nota: questo non è testato. Si presuppone che la raccolta di elementi figlio sia di tipo ICollection. (Di solito l'ho fatto IListe quindi il codice sembra un po 'diverso.) Ho anche rimosso tutte le astrazioni del repository per renderlo semplice.

Non so se questa sia una buona soluzione, ma credo che un certo tipo di duro lavoro su queste linee debba essere fatto per prendersi cura di tutti i tipi di cambiamenti nella raccolta di navigazione. Sarei anche felice di vedere un modo più semplice di farlo.


E se alcuni fossero cambiati? Significa che devo ancora rimuoverli e aggiungerli di nuovo?
Giaffa,

@Jon: No, ovviamente puoi anche aggiornare gli elementi esistenti. Ho aggiunto un esempio di come probabilmente aggiornerei la raccolta figlio, vedi la sezione Modifica sopra.
Slauma,

@Slauma: Lol, se sapessi che modificherai la tua risposta non scriverei la mia risposta ...
Ladislav Mrnka

@Ladislav: No, no, sono felice che tu abbia scritto la tua risposta. Ora almeno so che non è una totale assurdità e troppo complicato quello che ho fatto sopra.
Slauma,

1
Aggiungerei una condizione quando si recupera l'originaleChildItem nella foreach: ... Where (c => c.ID == childItem.ID && c.ID! = 0) altrimenti restituirà i nuovi figli aggiunti se childItem.ID == 0.
perfect_element

116

Il motivo per cui ti trovi di fronte è dovuto alla differenza tra composizione e aggregazione .

Nella composizione, l'oggetto figlio viene creato quando viene creato il genitore e viene distrutto quando il genitore viene distrutto . Quindi la sua vita è controllata dal suo genitore. ad esempio un post sul blog e i suoi commenti. Se un post viene eliminato, i suoi commenti dovrebbero essere eliminati. Non ha senso avere commenti per un post che non esiste. Lo stesso vale per gli ordini e gli articoli dell'ordine.

In aggregazione, l'oggetto figlio può esistere indipendentemente dal suo genitore . Se il genitore viene distrutto, l'oggetto figlio può ancora esistere, poiché potrebbe essere aggiunto in seguito a un genitore diverso. es: la relazione tra una playlist e le canzoni in quella playlist. Se la playlist viene eliminata, i brani non devono essere eliminati. Possono essere aggiunti a una playlist diversa.

Il modo in cui Entity Framework differenzia le relazioni di aggregazione e composizione è il seguente:

  • Per composizione: prevede che l'oggetto figlio abbia una chiave primaria composita (ParentID, ChildID). Questo è in base alla progettazione in quanto gli ID dei bambini dovrebbero rientrare nell'ambito dei genitori.

  • Per aggregazione: si aspetta che la proprietà della chiave esterna nell'oggetto figlio sia nullable.

Quindi, il motivo per cui stai riscontrando questo problema è dovuto al modo in cui hai impostato la chiave primaria nella tabella figlio. Dovrebbe essere composito, ma non lo è. Pertanto, Entity Framework vede questa associazione come aggregazione, il che significa che quando rimuovi o cancelli gli oggetti figlio, non eliminerà i record figlio. Rimuoverà semplicemente l'associazione e imposterà la colonna della chiave esterna corrispondente su NULL (in modo che i record secondari possano essere successivamente associati a un altro genitore). Poiché la tua colonna non consente NULL, ottieni l'eccezione che hai menzionato.

soluzioni:

1- Se si dispone di un valido motivo per non voler utilizzare una chiave composita, è necessario eliminare esplicitamente gli oggetti figlio. E questo può essere fatto più semplice delle soluzioni suggerite in precedenza:

context.Children.RemoveRange(parent.Children);

2- In caso contrario, impostando la chiave primaria corretta sulla tabella figlio, il codice apparirà più significativo:

parent.Children.Clear();

9
Ho trovato questa spiegazione molto utile.
Booji Boy,

7
Buona spiegazione per composizione vs aggregazione e come il framework di entità è correlato ad esso.
Crisalide,

Il numero 1 era la quantità minima di codice necessaria per risolvere il problema. Grazie!
Ryanulit,

73

Questo è un grosso problema. Ciò che effettivamente accade nel tuo codice è questo:

  • Tu carichi Parent dal database e si ottiene un'entità collegata
  • Sostituisci la sua raccolta figlio con una nuova raccolta di figli distaccati
  • Le modifiche vengono salvate ma durante questa operazione tutti i bambini vengono considerati aggiunti perché EF non li conosceva fino a questo momento. Quindi EF cerca di impostare null su chiave esterna di bambini anziani e inserire tutti i nuovi figli => righe duplicate.

Ora la soluzione dipende davvero da cosa vuoi fare e come ti piacerebbe farlo?

Se si utilizza ASP.NET MVC, è possibile provare a utilizzare UpdateModel o TryUpdateModel .

Se vuoi semplicemente aggiornare manualmente i bambini esistenti, puoi semplicemente fare qualcosa del tipo:

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

Il collegamento non è effettivamente necessario (impostando lo stato su Modified collegherà anche l'entità) ma mi piace perché rende il processo più ovvio.

Se vuoi modificare esistenti, eliminare quelli esistenti e inserire nuovi figli devi fare qualcosa del tipo:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();

1
Ma c'è una tua osservazione interessante sull'uso .Clone(). Hai in mente che a ChildItemha altre proprietà di navigazione secondarie? Ma in quel caso, non vorremmo che l'intero sotto-grafico fosse collegato al contesto poiché ci aspetteremmo che tutti i bambini secondari siano nuovi oggetti se il bambino stesso è nuovo? (Beh, potrebbe essere diverso da modello a modello, ma supponiamo che i bambini secondari siano "dipendenti" dal bambino come i bambini siano dipendenti dal genitore.)
Slauma

Probabilmente richiederebbe un clone "intelligente".
Ladislav Mrnka,

1
Cosa succede se non vuoi avere una collezione per bambini nel tuo contesto? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed,

1
parent.ChildItems.Remove (bambino); context.Childs.Remove (bambino); Questa doppia rimozione risolta può emettere, GRAZIE. Perché abbiamo bisogno di entrambe le rimozioni? Perché rimuoverlo solo da parent.ChildItems non è una spesa sufficiente poiché i bambini vivono solo come bambini?
Fernando Torres,

40

Ho trovato questa risposta molto più utile per lo stesso errore. Sembra che a EF non piaccia quando rimuovi, preferisce Elimina.

È possibile eliminare una raccolta di record allegati a un record come questo.

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

Nell'esempio, tutti i record Dettagli associati a un Ordine hanno il loro Stato impostato su Elimina. (In preparazione per aggiungere nuovamente i dettagli aggiornati, come parte di un aggiornamento dell'ordine)


Credo che sia la risposta corretta.
desmati,

soluzione logica e semplice.
Sairfan,

19

Non ho idea del perché le altre due risposte siano così popolari!

Credo che tu abbia ragione nel presumere che il framework ORM dovrebbe gestirlo - dopo tutto, questo è ciò che promette di offrire. Altrimenti il ​​tuo modello di dominio viene danneggiato da problemi di persistenza. NHibernate lo gestisce felicemente se si configurano correttamente le impostazioni della cascata. In Entity Framework è anche possibile, si aspettano solo che tu segua standard migliori quando configuri il tuo modello di database, specialmente quando devono dedurre ciò che dovrebbe essere fatto a cascata:

Devi definire correttamente la relazione genitore - figlio utilizzando una " relazione di identificazione ".

Se lo fai, Entity Framework sa che l'oggetto figlio è identificato dal genitore, e quindi deve essere una situazione "cascata-cancella-orfani".

Oltre a quanto sopra, potrebbe essere necessario (dall'esperienza NHibernate)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

invece di sostituire completamente l'elenco.

AGGIORNARE

Il commento di @ Slauma mi ha ricordato che le entità distaccate sono un'altra parte del problema generale. Per risolverlo, puoi adottare l'approccio dell'utilizzo di un raccoglitore di modelli personalizzato che costruisce i tuoi modelli tentando di caricarlo dal contesto. Questo post sul blog mostra un esempio di cosa intendo.


L'installazione come relazione di identificazione non è di aiuto in quanto lo scenario nella domanda deve occuparsi di entità distaccate ( "il mio nuovo elenco che proviene dalla vista MVC" ). Devi ancora caricare i figli originali dal DB, trovare gli elementi rimossi in quella raccolta in base alla raccolta staccata e quindi rimuoverli dal DB. L'unica differenza è che con una relazione identificativa puoi chiamare parent.ChildItems.Removeinvece di_dbContext.ChildItems.Remove . Non esiste ancora (EF <= 6) nessun supporto integrato da EF per evitare codici lunghi come quello delle altre risposte.
Slauma,

Capisco il tuo punto. Tuttavia, credo con un raccoglitore modello personalizzato che carica l'entità dal contesto o restituisce una nuova istanza che l'approccio sopra avrebbe funzionato. Aggiornerò la mia risposta per suggerire quella soluzione.
Andre Luus,

Sì, è possibile utilizzare un raccoglitore di modelli ma ora è necessario eseguire le operazioni dalle altre risposte nel raccoglitore di modelli. Sposta semplicemente il problema dal livello repo / service al raccoglitore di modelli. Almeno, non vedo una vera semplificazione.
Slauma,

La semplificazione è l'eliminazione automatica di entità orfane. Tutto ciò di cui hai bisogno nel raccoglitore di modelli è un equivalente generico direturn context.Items.Find(id) ?? new Item()
Andre Luus,

Un buon feedback per il team EF, purtroppo la soluzione proposta non risolve nulla in EF land.
Chris Moschini,

9

Se stai utilizzando AutoMapper con Entity Framework sulla stessa classe, potresti riscontrare questo problema. Ad esempio, se la tua classe è

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

Questo tenterà di copiare entrambe le proprietà. In questo caso, ClassBId non è Nullable. Poiché AutoMapper copierà destination.ClassB = input.ClassB;ciò causerà un problema.

Imposta AutoMapper su Ignora ClassBproprietà.

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId

Sono di fronte a un problema simile con automapper, ma questo non funziona per me :( See stackoverflow.com/q/41430679/613605
J86

4

Ho appena avuto lo stesso errore. Ho due tabelle con una relazione figlio principale, ma ho configurato un "on delete cascade" sulla colonna chiave esterna nella definizione della tabella della tabella figlio. Pertanto, quando elimino manualmente la riga padre (tramite SQL) nel database, verranno automaticamente eliminate le righe figlio.

Tuttavia, ciò non ha funzionato in EF, si è verificato l'errore descritto in questa discussione. Il motivo era che nel modello di dati della mia entità (file edmx) le proprietà dell'associazione tra la tabella padre e quella figlio non erano corrette. L' End1 OnDeleteopzione è stata configurata per esserenone ("End1" nel mio modello è la fine che ha una molteplicità di 1).

Ho cambiato manualmente l' End1 OnDeleteopzione in Cascadee di quanto ha funzionato. Non so perché EF non sia in grado di rilevarlo quando aggiorno il modello dal database (ho un primo modello di database).

Per completezza, ecco come appare il mio codice da eliminare:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

Se non avessi definito l'eliminazione a cascata, avrei dovuto eliminare manualmente le righe figlio prima di eliminare la riga principale.


4

Ciò accade perché l'Entità figlio è contrassegnata come Modificata anziché Eliminata.

E la modifica che EF esegue all'entità figlio quando parent.Remove(child)viene eseguita, sta semplicemente impostando il riferimento al suo genitore null.

È possibile controllare EntityState del bambino digitando il seguente codice nella finestra immediata di Visual Studio quando si verifica l'eccezione, dopo l'esecuzione SaveChanges():

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

dove X deve essere sostituito dall'entità eliminata.

Se non si ha accesso a ObjectContextda eseguire _context.ChildEntity.Remove(child), è possibile risolvere questo problema rendendo la chiave esterna parte della chiave primaria nella tabella figlio.

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

In questo modo, se eseguito parent.Remove(child), EF contrassegnerà correttamente l'Entità come Eliminata.


2

Questo tipo di soluzione ha funzionato per me:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

È importante dire che questo elimina tutti i record e li inserisce di nuovo. Ma per il mio caso (meno di 10) va bene.

Spero possa essere d'aiuto.


Il reinserimento avviene con nuovi ID o mantiene in primo luogo gli ID del bambino che avevano?
Pepito Fernandez,

2

Oggi ho riscontrato questo problema e volevo condividere la mia soluzione. Nel mio caso, la soluzione era eliminare gli elementi figlio prima di ottenere il genitore dal database.

In precedenza lo stavo facendo come nel codice qui sotto. Riceverò quindi lo stesso errore elencato in questa domanda.

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

Ciò che ha funzionato per me è innanzitutto ottenere gli elementi figlio, utilizzando parentId (chiave esterna) e quindi eliminarli. Quindi posso ottenere il genitore dal database e, a quel punto, non dovrebbe più avere elementi figlio e posso aggiungere nuovi elementi figlio.

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here

2

È necessario cancellare manualmente la raccolta ChildItems e aggiungere nuovi elementi in essa:

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

Successivamente è possibile chiamare il metodo di estensione DeleteOrphans che gestirà con entità orfane (deve essere chiamato tra i metodi DetectChanges e SaveChanges).

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}

Questo ha funzionato bene per me. Ho solo bisogno di aggiungere context.DetectChanges();.
Andy Edinborough,

1

Ho provato queste soluzioni e molte altre, ma nessuna di queste ha funzionato. Poiché questa è la prima risposta su Google, aggiungerò la mia soluzione qui.

Il metodo che ha funzionato bene per me è stato quello di togliere le relazioni dalla scena durante gli commit, quindi non c'era nulla da rovinare per EF. L'ho fatto ritrovando l'oggetto genitore nel DBContext ed eliminandolo. Poiché le proprietà di navigazione dell'oggetto ritrovato sono tutte nulle, le relazioni dei bambini vengono ignorate durante il commit.

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

Si noti che ciò presuppone che le chiavi esterne siano impostate con ON DELETE CASCADE, quindi quando la riga padre viene rimossa, i figli verranno ripuliti dal database.


1

Ho usato la soluzione di Mosh , ma per me non era ovvio come implementare correttamente la chiave di composizione nel codice.

Quindi ecco la soluzione:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}

1

Ho avuto lo stesso problema, ma sapevo che aveva funzionato bene in altri casi, quindi ho ridotto il problema a questo:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems aveva una chiave primaria composita (parentId + alcune colonne locali) e funzionava bene
  • Gli oggetti problematici avevano la loro chiave primaria a colonna singola e parentId era solo un FK. Ciò stava causando l'eccezione dopo Clear ().

Tutto quello che dovevo fare era rendere ParentId una parte del PK composito per indicare che i bambini non potevano esistere senza un genitore. Ho usato il modello DB-first, ho aggiunto il PK e contrassegnato la colonna parentId come EntityKey (quindi, ho dovuto aggiornarlo sia in DB che in EF - non sono sicuro che solo EF fosse sufficiente).

Ho fatto RequestId parte del PK E quindi aggiornato il modello EF E impostare l'altra proprietà come parte di Entity Key

Una volta che ci pensi, è una distinzione molto elegante che EF usa per decidere se i bambini "hanno un senso" senza un genitore (in questo caso Clear () non li cancellerà e genererà un'eccezione a meno che non imposti ParentId su qualcos'altro / speciale ) o, come nella domanda originale, prevediamo che gli elementi vengano eliminati dopo essere stati rimossi dal padre.


0

Questo problema si verifica perché si tenta di eliminare la tabella padre ancora sono presenti i dati della tabella figlio. Risolviamo il problema con l'aiuto dell'eliminazione a cascata.

Nel modello Crea metodo nella classe dbcontext.

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

Successivamente, nella nostra chiamata API

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

L' opzione di eliminazione in cascata elimina la tabella figlio relativa al genitore e al genitore con questo semplice codice. Fallo provare in questo modo semplice.

Rimuovi intervallo utilizzato per eliminare l'elenco dei record nel database. Grazie


0

Ho anche risolto il mio problema con la risposta di Mosh e ho pensato che la risposta di Peter B fosse un po 'da quando ha usato un enum come chiave esterna. Ricorda che dovrai aggiungere una nuova migrazione dopo aver aggiunto questo codice.

Posso anche raccomandare questo post sul blog per altre soluzioni:

http://www.kianryan.co.uk/2013/03/orphaned-child/

Codice:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

0

Utilizzando la soluzione di Slauma, ho creato alcune funzioni generiche per aiutare ad aggiornare gli oggetti figlio e le raccolte di oggetti figlio.

Tutti i miei oggetti persistenti implementano questa interfaccia

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

Con questo ho implementato queste due funzioni nel mio repository

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

Per usarlo faccio quanto segue:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

Spero che questo ti aiuti


EXTRA: potresti anche creare una classe DbContextExtentions separata (o la tua inferface del tuo contesto):

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

e usalo come:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

È inoltre possibile creare una classe di estensione per il proprio contesto con queste funzioni:
Bluemoon74,

0

Ho avuto lo stesso problema quando ho intenzione di eliminare il mio record di quanto si sia verificato un problema, perché questa soluzione del problema è che quando hai intenzione di eliminare il tuo record di quanto ti manchi qualcosa prima di eliminare l'intestazione / record principale devi scrivere nel codice per cancellare i dettagli prima dell'intestazione / Master Spero che il problema venga risolto.


0

Se stai usando Auto mapper e stai affrontando il problema che segue è la buona soluzione, funziona per me

https://www.codeproject.com/Articles/576393/Solutionplusto-aplus-Theplusoperationplusfailed

Poiché il problema è che stiamo mappando le proprietà di navigazione null e in realtà non abbiamo bisogno che vengano aggiornate sull'entità poiché non sono state modificate nel contratto, dobbiamo ignorarle nella definizione di mappatura:

ForMember(dest => dest.RefundType, opt => opt.Ignore())

Quindi il mio codice è finito così:

Mapper.CreateMap<MyDataContract, MyEntity>
ForMember(dest => dest.NavigationProperty1, opt => opt.Ignore())
ForMember(dest => dest.NavigationProperty2, opt => opt.Ignore())
.IgnoreAllNonExisting();

-1

Ho riscontrato questo problema prima di diverse ore e ho provato tutto, ma nel mio caso la soluzione era diversa da quella sopra elencata.

Se si utilizza un'entità già recuperata dal database e si tenta di modificarne i bambini, si verificherà l'errore, ma se si ottiene una nuova copia dell'entità dal database non dovrebbero esserci problemi. Non usare questo:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

Usa questo:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.