210 votes

La relation n'a pas pu être modifiée car une ou plusieurs propriétés de clé étrangère ne sont pas nullables.

J'obtiens cette erreur quand je GetById() sur une entité, puis définissez la collecte de l'enfant entités à ma nouvelle liste qui vient de la MVC vue.

L'opération a échoué: Le la relation ne peut pas être changé parce que l'un ou plusieurs de la clé étrangère les propriétés n'est pas les valeurs null. Lorsqu'un modification est apportée à une relation, le liées clé étrangère propriété est définie à une valeur null. Si la clé étrangère ne pas en charge les valeurs null, une nouvelle la relation doit être défini, l' clé étrangère de la propriété doit être attribué une autre valeur non nulle, ou la sans rapport avec l'objet doit être supprimé.

Je ne comprends pas très bien cette ligne:

La relation ne peut pas être changé parce que l'un ou plusieurs de la clé étrangère les propriétés n'est pas les valeurs null.

Pourquoi voudrais-je changer la relation entre les 2 entités? Il doit rester le même tout au long de la durée de vie de l'ensemble de l'application.

Le code de l'exception se produit sur est simple affectation modifiée classes enfant dans une collection pour les parents de la classe. Ce serait espérons accueillir de retrait des enfants des classes, l'ajout de nouveaux et de modifications. J'aurais pensé à Entity Framework gère cela.

Les lignes de code peut être distillé:

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

169voto

Slauma Points 76561

Vous devez supprimer les anciens articles thisParent.ChildItems un par un manuellement. Entity Framework ne pas le faire pour vous. Enfin, elle ne peut pas décider ce que vous voulez faire avec l'ancien enfant des éléments - si vous voulez les jeter, ou si vous voulez les garder et de les affecter à d'autres sociétés affiliées. Vous devez dire à l'Entité Cadre de votre décision. Mais l'un de ces deux décisions que vous AVEZ à faire, car les enfants ne peuvent pas vivre seuls, sans une référence à un parent dans la base de données (en raison de la contrainte de clé étrangère). C'est ce que l'exception dit.

Modifier

Ce que je ferais si l'enfant les éléments pourraient être ajoutées, mises à jour et supprimées:

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

Remarque: Ce n'est pas testé. C'est en supposant que l'enfant de l'élément de la collection est de type ICollection. (J'ai l'habitude de IList et que le code ressemble un peu à d'autres.) J'ai aussi dépouillé de tous référentiel des abstractions de garder les choses simples.

Je ne sais pas si c'est une bonne solution, mais je crois que certains types de travail acharné, le long de ces lignes doit être fait pour prendre soin de toutes sortes de changements dans la navigation de la collection. Je serais heureux de voir un moyen plus facile.

76voto

Ladislav Mrnka Points 218632

C'est un très gros problème. Ce qui se passe réellement dans votre code est:

  • Vous chargez Parent de la base de données et d'obtenir un joint entité
  • Vous remplacer son enfant de la collection avec la nouvelle collection de détaché enfants
  • Vous enregistrez les modifications, mais au cours de cette opération, tous les enfants sont considérés comme ajouté becasue EF n'ai pas maintenant parler d'eux jusqu'à ce temps. Donc EF tente de définir la valeur null à la clé étrangère de vieux enfants et insérer tous les nouveaux enfants => les lignes en double.

Maintenant, la solution dépend vraiment de ce que vous voulez faire et comment voulez-vous faire?

Si vous utilisez ASP.NET MVC vous pouvez essayer d'utiliser UpdateModel ou tryupdatemodel pour mettre.

Si vous souhaitez simplement mettre à jour les enfants manuellement, vous pouvez simplement faire quelque chose comme:

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

context.SaveChanges();

Fixation n'est pas réellement nécessaire (réglage de l'etat à l' Modified sera également attacher de l'entité), mais je l'aime parce qu'il rend le processus plus évident.

Si vous souhaitez modifier, supprimer et insérer de nouvelles childs, vous devez faire quelque chose comme:

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

41voto

Greg Little Points 126

J'ai trouvé cette réponse beaucoup plus utile pour la même erreur. Il semble que EF ne l'aime pas quand vous supprimez, il préfère Supprimer.

Vous pouvez supprimer une collection d'enregistrements attachés à un enregistrement comme celui-ci.

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

Dans l'exemple, l'état de tous les enregistrements de détail attachés à une commande est défini sur Supprimer. (En préparation pour ajouter des détails mis à jour, dans le cadre d'une mise à jour de la commande)

20voto

Andre Luus Points 1603

Je n'ai aucune idée pourquoi les deux autres réponses sont si populaires!

Je crois que vous étiez en droit de supposer l'ORM doit gérer elle - après tout, qu'est ce qu'elle promet. Sinon, votre modèle de domaine est corrompu par la persistance. NHibernate gère avec plaisir si vous le programme d'installation de la cascade paramètres correctement. Dans le Cadre de l'Entité, il est également possible, ils attendent de vous que vous suivez de meilleures normes lors de la configuration de votre modèle de base de données, surtout lorsqu'ils doivent deviner ce que la cascade doit être fait:

Vous devez définir la relation parent - enfant correctement à l'aide d'un "identifiant de la relation".

Si vous faites cela, Entity Framework sait l'enfant objet est identifié par les parents, et par conséquent, il doit être une "cascade-supprimer-les orphelins" de la situation.

Autres que celles ci-dessus, vous pourriez avoir besoin pour (à partir de NHibernate expérience)

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

au lieu de remplacer la liste entièrement.

Mise à JOUR

@Slauma commentaire m'a rappelé que détachée entités sont une autre partie de l'ensemble du problème. Pour le résoudre, vous pouvez prendre l'approche de l'utilisation d'un modèle de liaison personnalisé que les constructions de vos modèles en tentant de le charger à partir du contexte. Ce blog montre un exemple de ce que je veux dire.

1voto

steve Points 70

J'ai essayé ces solutions, et beaucoup d'autres, mais aucun d'entre eux assez travaillé. Puisque c'est la première réponse sur google, je vais ajouter ma solution ici.

La méthode qui a bien fonctionné pour moi a été de prendre les relations de l'image pendant engage, donc il n'y avait rien pour EF vis. Je l'ai fait en re-trouver l'objet parent dans le DBContext, et de le supprimer. Depuis le re-trouve l'objet les propriétés de navigation sont tous nuls, les enfants relations sont ignorés lors de la validation.

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

Notez que cela suppose que les clés étrangères sont le programme d'installation avec on DELETE CASCADE, de sorte que lorsque la ligne parent est supprimé, les enfants seront nettoyés par la base de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X