it-swarm.com.de

Die Beziehung konnte nicht geändert werden, da eine oder mehrere der Fremdschlüsseleigenschaften nicht nullfähig ist

Ich erhalte diese Fehlermeldung, wenn ich GetById () für eine Entität ablege und dann die Auflistung der untergeordneten Entitäten auf meine neue Liste setze, die aus der MVC-Ansicht stammt.

Die Operation ist fehlgeschlagen: Die Beziehung konnte nicht geändert werden weil einer oder mehrere der Fremdschlüssel Eigenschaften ist nicht nullfähig. Wenn ein Änderung wird an einer Beziehung vorgenommen, der verwandte Fremdschlüsseleigenschaft wird auf .__ gesetzt. ein Nullwert Wenn der Fremdschlüssel unterstützt keine NULL-Werte, eine neue Beziehung muss definiert werden, die Fremdschlüsseleigenschaft muss zugewiesen werden ein anderer Wert ungleich Null oder der Nicht verbundenes Objekt muss gelöscht werden.

Ich verstehe diese Zeile nicht ganz:

Die Beziehung konnte nicht geändert werden weil einer oder mehrere der Fremdschlüssel Eigenschaften ist nicht nullfähig.

Warum sollte ich die Beziehung zwischen zwei Entitäten ändern? Es sollte während der gesamten Lebensdauer der Anwendung unverändert bleiben.

Der Code, für den die Ausnahme auftritt, besteht einfach darin, der vorhandenen übergeordneten Klasse geänderte untergeordnete Klassen in einer Auflistung zuzuweisen. Dies würde hoffentlich für das Entfernen von Kinderklassen, das Hinzufügen neuer und Modifikationen sorgen. Ich hätte gedacht, dass das Entity Framework damit fertig wird.

Die Codezeilen können destilliert werden:

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

Sie sollten alte untergeordnete Elemente thisParent.ChildItems manuell nacheinander löschen. Entity Framework macht das nicht für Sie. Es kann schließlich nicht entscheiden, was Sie mit den alten untergeordneten Elementen tun möchten - ob Sie sie wegwerfen möchten oder ob Sie sie behalten und anderen übergeordneten Entitäten zuweisen möchten. Sie müssen Entity Framework Ihre Entscheidung mitteilen. Eine dieser beiden Entscheidungen müssen Sie jedoch treffen, da die untergeordneten Entitäten nicht ohne Bezug zu einem übergeordneten Element in der Datenbank alleine leben können (aufgrund der Fremdschlüsseleinschränkung). Das sagt im Grunde die Ausnahme. 

Bearbeiten

Was würde ich tun, wenn untergeordnete Elemente hinzugefügt, aktualisiert und gelöscht werden könnten:

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();
}

Hinweis: Dies wird nicht getestet. Es wird davon ausgegangen, dass die untergeordnete Elementauflistung vom Typ ICollection ist. (Ich habe normalerweise IList und dann sieht der Code ein bisschen anders aus.) Ich habe auch alle Repository-Abstraktionen entfernt, um es einfach zu halten.

Ich weiß nicht, ob dies eine gute Lösung ist, aber ich glaube, dass in dieser Hinsicht harte Arbeit geleistet werden muss, um alle Arten von Änderungen in der Navigationssammlung zu berücksichtigen. Ich würde mich auch über einen einfacheren Weg freuen.

145
Slauma

Der Grund, warum Sie sich diesem Problem stellen müssen, liegt im Unterschied zwischen composition und aggregation

In der Komposition wird das untergeordnete Objekt erstellt, wenn das übergeordnete Objekt erstellt wird, und es wird zerstört, wenn das übergeordnete Objekt zerstört wird. So wird seine Lebensdauer von den Eltern gesteuert. z.B. Ein Blogbeitrag und seine Kommentare. Wenn ein Beitrag gelöscht wird, sollten seine Kommentare gelöscht werden. Es macht keinen Sinn, Kommentare zu einem Beitrag zu haben, der nicht existiert. Gleiches für Bestellungen und Bestellartikel.

In der Aggregation kann das untergeordnete Objekt unabhängig von seinem übergeordneten Objekt existieren. Wenn das übergeordnete Objekt zerstört wird, kann das untergeordnete Objekt noch vorhanden sein, da es später zu einem anderen übergeordneten Objekt hinzugefügt werden kann. z. B. die Beziehung zwischen einer Wiedergabeliste und den Titeln in dieser Wiedergabeliste. Wenn die Wiedergabeliste gelöscht wird, sollten die Titel nicht gelöscht werden. Sie können zu einer anderen Wiedergabeliste hinzugefügt werden.

Das Entity Framework unterscheidet Aggregations- und Kompositionsbeziehungen folgendermaßen:

  • Für Komposition: Es erwartet, dass das untergeordnete Objekt einen zusammengesetzten Primärschlüssel (ParentID, ChildID) hat. Dies ist beabsichtigt, da die IDs der Kinder im Bereich ihrer Eltern liegen sollten. 

  • Für die Aggregation: Es wird erwartet, dass die Fremdschlüsseleigenschaft im untergeordneten Objekt nullfähig ist.

Der Grund für dieses Problem liegt also darin, wie Sie Ihren Primärschlüssel in Ihrer untergeordneten Tabelle festgelegt haben. Es sollte zusammengesetzt sein, ist es aber nicht. Entity Framework betrachtet diese Zuordnung also als Aggregation. Wenn Sie also die untergeordneten Objekte entfernen oder löschen, werden die untergeordneten Datensätze nicht gelöscht. Es entfernt einfach die Zuordnung und setzt die entsprechende Fremdschlüsselspalte auf NULL (damit diese untergeordneten Datensätze später einem anderen übergeordneten Element zugeordnet werden können). Da Ihre Spalte NULL nicht zulässt, wird die von Ihnen erwähnte Ausnahme angezeigt. 

Lösungen:

1- Wenn Sie aus gutem Grund keinen zusammengesetzten Schlüssel verwenden möchten, müssen Sie die untergeordneten Objekte explizit löschen. Und dies ist einfacher als die zuvor vorgeschlagenen Lösungen:

context.Children.RemoveRange(parent.Children);

2- Andernfalls wird der Code aussagekräftiger, wenn Sie den richtigen Primärschlüssel für Ihre untergeordnete Tabelle festlegen:

parent.Children.Clear();
81
Mosh

Das ist ein sehr großes Problem. Was in Ihrem Code tatsächlich passiert, ist Folgendes:

  • Sie laden Parent aus der Datenbank und erhalten eine angefügte Entität
  • Sie ersetzen die untergeordnete Sammlung durch eine neue Sammlung getrennter Kinder
  • Sie speichern die Änderungen, aber während dieser Operation werden alle Kinder als added betrachtet, da EF bis zu diesem Zeitpunkt noch nichts davon wusste. EF versucht also, den Fremdschlüssel alter Kinder auf Null zu setzen und alle neuen Kinder einzufügen => doppelte Zeilen.

Nun hängt die Lösung wirklich davon ab, was Sie tun möchten und wie Sie dies tun möchten. 

Wenn Sie ASP.NET MVC verwenden, können Sie versuchen, UpdateModel oder TryUpdateModel zu verwenden.

Wenn Sie vorhandene Kinder nur manuell aktualisieren möchten, können Sie einfach Folgendes tun:

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

context.SaveChanges();

Das Anfügen ist eigentlich nicht erforderlich (durch Festlegen des Status auf Modified wird auch die Entität angehängt), aber ich mag es, weil es den Prozess offensichtlicher macht.

Wenn Sie vorhandene ändern, vorhandene löschen und neue untergeordnete Elemente einfügen möchten, müssen Sie Folgendes tun:

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();
67
Ladislav Mrnka

Ich fand this antworte viel hilfreicher auf den gleichen Fehler ..__ Es scheint, dass EF es nicht gefällt, wenn Sie Remove entfernen.

Sie können eine Sammlung von Datensätzen löschen, die diesem Datensatz angefügt sind.

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

In diesem Beispiel ist der Status aller an einen Auftrag angefügten Detailsätze auf Löschen gesetzt. (In Vorbereitung, um aktualisierte Details als Teil einer Bestellaktualisierung hinzuzufügen)

35
Greg Little

Ich habe keine Ahnung, warum die beiden anderen Antworten so beliebt sind!

Ich glaube, Sie haben zu Recht davon ausgegangen, dass das ORM-Framework damit umgehen sollte - schließlich verspricht es dies auch. Andernfalls wird Ihr Domain-Modell durch Persistenzbedenken beschädigt. NHibernate schafft dies problemlos, wenn Sie die Kaskadeneinstellungen korrekt vornehmen. In Entity Framework ist es auch möglich, dass Sie beim Einrichten Ihres Datenbankmodells bessere Standards einhalten, insbesondere dann, wenn Sie ableiten müssen, welche Kaskadierung durchgeführt werden soll:

Sie müssen definieren Sie die Eltern-Kind-Beziehung korrekt mit einem " identifizierende Beziehung ".

Wenn Sie dies tun, weiß Entity Framework, dass das untergeordnete Objekt vom übergeordneten Objekt identifiziert wird , und daher muss es sich um eine "Kaskadenlösch-Waisen" -Situation handeln.

Andernfalls müssen Sie möglicherweise (aus NHibernate-Erfahrung)

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

anstatt die Liste komplett zu ersetzen.

UPDATE

@ Slaumas Kommentar erinnerte mich daran, dass abgelöste Einheiten ein weiterer Teil des Gesamtproblems sind. Um dies zu lösen, können Sie einen benutzerdefinierten Modellordner verwenden, der Ihre Modelle erstellt, indem Sie versuchen, sie aus dem Kontext zu laden. Dieser Blog-Beitrag zeigt ein Beispiel dafür, was ich meine.

19
Andre Luus

Wenn Sie AutoMapper mit Entity Framework für dieselbe Klasse verwenden, kann dieses Problem auftreten. Zum Beispiel, wenn Ihre Klasse ist

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

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

Dadurch wird versucht, beide Eigenschaften zu kopieren. In diesem Fall ist ClassBId nicht nullfähig. Da AutoMapper destination.ClassB = input.ClassB; kopiert, wird dies zu einem Problem führen.

Stellen Sie Ihren AutoMapper auf Ignorieren der Eigenschaft ClassB ein.

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

Dies geschieht, weil die untergeordnete Entität als geändert und nicht als gelöscht markiert ist.

Die Änderung, die EF an der untergeordneten Entität vornimmt, wenn parent.Remove(child) ausgeführt wird, setzt einfach den Verweis auf die übergeordnete Entität auf null.

Sie können den EntityState des Kindes prüfen, indem Sie nach dem Ausführen von SaveChanges() den folgenden Code in das Direktfenster von Visual Studio eingeben, wenn die Ausnahme auftritt:

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

wobei X durch die gelöschte Entität ersetzt werden soll.

Wenn Sie keinen Zugriff auf die Variable ObjectContext haben, um _context.ChildEntity.Remove(child) auszuführen, können Sie dieses Problem lösen, indem Sie den Fremdschlüssel zu einem Teil des Primärschlüssels der untergeordneten Tabelle machen.

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

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

Auf diese Weise wird EF die Entität korrekt als gelöscht markieren, wenn Sie parent.Remove(child) ausführen.

4

Ich hatte gerade den gleichen Fehler .. __ Ich habe zwei Tabellen mit einer übergeordneten untergeordneten Beziehung, aber ich habe in der Tabellendefinition der untergeordneten Tabelle eine ___Kombination zum Löschen von Kaskaden konfiguriert. __ Wenn ich manuell Wenn Sie die übergeordnete Zeile (über SQL) in der Datenbank löschen, werden die untergeordneten Zeilen automatisch gelöscht.

Dies funktionierte jedoch nicht in EF, der in diesem Thread beschriebene Fehler zeigte sich jedoch .. .. Der Grund dafür war, dass in meinem Entitätsdatenmodell (edmx-Datei) die Eigenschaften der Zuordnung zwischen der übergeordneten und der untergeordneten Tabelle nicht zutrafen correct . Die End1 OnDelete-Option wurde als none konfiguriert ("End1" in meinem Modell ist das Ende mit einer Multiplizität von 1).

Ich änderte die End1 OnDelete-Option manuell in Cascade und dann funktionierte sie .__ Ich weiß nicht, warum EF dies nicht abrufen kann, wenn ich das Modell aus der Datenbank aktualisiere (ich habe ein erstes Datenbankmodell).

Der Vollständigkeit halber sieht mein Code zum Löschen so aus:

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

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

Wenn ich keine Kaskadenlöschung definiert hätte, müsste ich die untergeordneten Zeilen manuell löschen, bevor die übergeordnete Zeile gelöscht wird.

3
Martin

Sie müssen die ChildItems-Auflistung manuell löschen und neue Elemente anfügen: 

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

Danach können Sie die DeleteOrphans-Erweiterungsmethode aufrufen, die mit verwaisten Entitäten behandelt werden kann (sie muss zwischen den Methoden DetectChanges und SaveChanges aufgerufen werden). 

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);
    }
}
2
Sat

Ich bin heute auf dieses Problem gestoßen und wollte meine Lösung mitteilen. In meinem Fall bestand die Lösung darin, die untergeordneten Elemente zu löschen, bevor das übergeordnete Element aus der Datenbank abgerufen wurde.

Früher habe ich es wie im Code unten gemacht. Ich werde dann den gleichen Fehler in dieser Frage finden.

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

Was für mich funktioniert hat, ist, zuerst die untergeordneten Elemente mit der parentId (Fremdschlüssel) abzurufen und diese Elemente dann zu löschen. Dann kann ich das übergeordnete Element aus der Datenbank abrufen. Zu diesem Zeitpunkt sollten keine untergeordneten Elemente mehr vorhanden sein, und ich kann neue untergeordnete Elemente hinzufügen.

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
Dino Bansigan

Ich habe diese Lösungen und viele andere ausprobiert, aber keine davon hat sich bewährt. Da dies die erste Antwort auf Google ist, füge ich meine Lösung hier hinzu.

Die Methode, die für mich gut funktionierte, bestand darin, Beziehungen während Commits aus dem Bild zu nehmen, so dass EF nichts zu vermasseln vermochte. Ich tat dies, indem ich das übergeordnete Objekt in DBContext neu fand und löschte. Da die Navigationseigenschaften des wiedergefundenen Objekts alle null sind, werden die Beziehungen der Kinder beim Festschreiben ignoriert.

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

Beachten Sie, dass davon ausgegangen wird, dass die Fremdschlüssel mit ON DELETE CASCADE eingerichtet sind. Wenn die übergeordnete Zeile entfernt wird, werden die untergeordneten Elemente von der Datenbank bereinigt.

1
Steve

Diese Art von Lösung hat für mich den Trick gebracht:

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);

Es ist wichtig zu sagen, dass dies alle Datensätze löscht und sie erneut einfügt ..__ Aber für meinen Fall (weniger als 10) ist es in Ordnung.

Ich hoffe, es hilft.

Ich habe Moshs Lösung verwendet, aber mir war nicht klar, wie man den Kompositionsschlüssel zuerst korrekt in Code implementiert. 

Also hier ist die Lösung:

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
PeterB

Ich löste mein Problem auch mit Moshs Antwort und ich dachte PeterBs Antwort war ein bisschen, da es eine Aufzählung als Fremdschlüssel verwendete. Denken Sie daran, dass Sie nach dem Hinzufügen dieses Codes eine neue Migration hinzufügen müssen.

Ich kann diesen Blogpost auch für andere Lösungen empfehlen:

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

Code:

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
Ogglas

Ich habe dieses Problem schon vor mehreren Stunden gefunden und habe alles versucht, aber in meinem Fall war die Lösung anders als oben aufgeführt.

Wenn Sie eine bereits abgerufene Entität aus der Datenbank verwenden und versuchen, die untergeordneten Elemente zu ändern, tritt der Fehler auf. Wenn Sie jedoch eine neue Entität der Entität aus der Datenbank abrufen, sollten keine Probleme auftreten. __ Verwenden Sie Folgendes nicht:

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

Benutze das:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
0
Tanyo Ivanov

Mit der Lösung von Slauma erstellte ich einige generische Funktionen, um untergeordnete Objekte und Sammlungen untergeordneter Objekte zu aktualisieren.

Alle meine persistenten Objekte implementieren diese Schnittstelle

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

Damit habe ich diese beiden Funktionen in meinem Repository implementiert

    /// <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);
            }
        }
    }

Um es zu benutzen, mache ich folgendes:

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);

Hoffe das hilft


EXTRA: Sie können auch eine separate DbContextExtentions-Klasse (oder Ihre eigene Kontext-Inferface-Klasse) erstellen:

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);
            }
        }
    }
}

und benutze es wie:

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);
0
Bluemoon74

Dieses Problem tritt auf, weil versucht wird, die übergeordnete Tabelle zu löschen, obwohl noch untergeordnete Tabellendaten vorhanden sind . Wir lösen das Problem mit Hilfe der Kaskadenlöschung.

Im Modell Create-Methode in der Dbcontext-Klasse.  

 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);

Danach in unserem API-Aufruf

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

Cascade delete Option Mit diesem einfachen Code können Sie sowohl die übergeordnete als auch übergeordnete untergeordnete Tabelle löschen. Versuchen Sie es auf diese einfache Weise.

Entferne den Bereich, der zum Löschen der Liste der Datensätze in der Datenbank verwendet wurde Vielen Dank

0
Sowmiya V

Ich hatte das gleiche Problem, als ich meinen Datensatz löschen wollte, da ein Problem aufgetreten ist. Diese Problemlösung besteht darin, dass Sie, wenn Sie Ihren Datensatz löschen möchten, etwas verpassen, bevor Sie den Header/Stammsatz löschen, für den Sie Code schreiben müssen lösche seine Details vor dem Header/Master. Ich hoffe dein Problem wird gelöst.

0
Ghazi Hur