Saltar al contenido

La relación no se pudo cambiar porque una o más de las propiedades de la clave externa no aceptan valores NULL.

Solución:

Debes eliminar los elementos secundarios antiguos thisParent.ChildItems uno por uno manualmente. Entity Framework no hace eso por usted. Finalmente, no puede decidir qué desea hacer con los elementos secundarios antiguos, si desea desecharlos o si desea conservarlos y asignarlos a otras entidades principales. Debe informar a Entity Framework su decisión. Pero TIENE que tomar una de estas dos decisiones, ya que las entidades secundarias no pueden vivir solas sin una referencia a ningún padre en la base de datos (debido a la restricción de clave externa). Eso es básicamente lo que dice la excepción.

Editar

Qué haría si se pudieran agregar, actualizar y eliminar elementos secundarios:

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: esto no está probado. Se supone que la colección de elementos secundarios es de tipo ICollection. (Usualmente tengo IList y luego el código se ve un poco diferente). También eliminé todas las abstracciones del repositorio para mantenerlo simple.

No sé si esa es una buena solución, pero creo que se debe hacer algún tipo de trabajo duro en este sentido para encargarnos de todo tipo de cambios en la colección de navegación. También me alegraría ver una forma más sencilla de hacerlo.

La razón por la que te enfrentas a esto se debe a la diferencia entre composición y agregación.

En composición, el objeto hijo se crea cuando se crea el padre y se destruye cuando se destruye su padre. Entonces su vida está controlada por su padre. por ejemplo, una publicación de blog y sus comentarios. Si se elimina una publicación, se deben eliminar sus comentarios. No tiene sentido tener comentarios para una publicación que no existe. Lo mismo para pedidos y artículos de pedido.

En agregación, el objeto hijo puede existir independientemente de su padre. Si se destruye el padre, el objeto hijo aún puede existir, ya que se puede agregar a un padre diferente más adelante. por ejemplo: la relación entre una lista de reproducción y las canciones en esa lista de reproducción. Si se elimina la lista de reproducción, las canciones no se deben eliminar. Pueden agregarse a una lista de reproducción diferente.

La forma en que Entity Framework diferencia las relaciones de agregación y composición es la siguiente:

  • Para la composición: espera que el objeto secundario tenga una clave primaria compuesta (ParentID, ChildID). Esto es por diseño, ya que las identificaciones de los niños deben estar dentro del alcance de sus padres.

  • Para agregación: espera que la propiedad de clave externa en el objeto secundario sea anulable.

Entonces, la razón por la que tiene este problema es por cómo configuró su clave principal en su tabla secundaria. Debería ser compuesto, pero no lo es. Entonces, Entity Framework ve esta asociación como agregación, lo que significa que cuando elimina o borra los objetos secundarios, no eliminará los registros secundarios. Simplemente eliminará la asociación y establecerá la columna de clave externa correspondiente en NULL (para que esos registros secundarios se puedan asociar más tarde con un padre diferente). Dado que su columna no permite NULL, obtiene la excepción que mencionó.

Soluciones:

1- Si tiene una razón importante para no querer utilizar una clave compuesta, debe eliminar los objetos secundarios de forma explícita. Y esto se puede hacer más simple que las soluciones sugeridas anteriormente:

context.Children.RemoveRange(parent.Children);

2- De lo contrario, al configurar la clave primaria adecuada en su tabla secundaria, su código se verá más significativo:

parent.Children.Clear();

Este es un problema muy grande. Lo que realmente sucede en su código es esto:

  • Tu cargas Parent de la base de datos y obtener una entidad adjunta
  • Reemplaza su colección de niños con una nueva colección de niños separados
  • Guarda los cambios, pero durante esta operación, todos los niños se consideran como adicional porque EF no los conocía hasta ese momento. Entonces EF intenta establecer nulo en la clave externa de los niños antiguos e insertar todos los niños nuevos => filas duplicadas.

Ahora bien, la solución realmente depende de lo que quieras hacer y de cómo te gustaría hacerlo.

Si está utilizando ASP.NET MVC, puede intentar utilizar UpdateModel o TryUpdateModel.

Si solo desea actualizar los niños existentes manualmente, simplemente puede hacer algo como:

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

context.SaveChanges();

En realidad, no es necesario adjuntar (establecer el estado en Modified también adjuntará la entidad) pero me gusta porque hace que el proceso sea más obvio.

Si desea modificar el existente, eliminar el existente e insertar nuevos hijos, debe hacer algo como:

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();
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *