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

Resuelto jaffa asked hace 13 años • 21 respuestas

Recibo este error cuando estoy GetById()en una entidad y luego configuro la colección de entidades secundarias en mi nueva lista que proviene de la vista MVC.

La operación falló: la relación no se pudo cambiar porque una o más de las propiedades de clave externa no aceptan valores NULL. Cuando se realiza un cambio en una relación, la propiedad de clave externa relacionada se establece en un valor nulo. Si la clave externa no admite valores nulos, se debe definir una nueva relación, se debe asignar a la propiedad de clave externa otro valor no nulo o se debe eliminar el objeto no relacionado.

No entiendo muy bien esta línea:

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

¿Por qué cambiaría la relación entre 2 entidades? Debe permanecer igual durante toda la vida útil de toda la aplicación.

El código en el que se produce la excepción es simplemente asignar clases secundarias modificadas en una colección a la clase principal existente. Es de esperar que esto permita eliminar clases secundarias, agregar otras nuevas y realizar modificaciones. Pensé que Entity Framework maneja esto.

Las líneas de código se pueden resumir en:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
jaffa avatar Apr 04 '11 20:04 jaffa
Aceptado

Debes eliminar los elementos secundarios antiguos thisParent.ChildItemsuno por uno manualmente. Entity Framework no hace eso por usted. Finalmente, no puede decidir qué quiere 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 DEBE 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. (Por lo general, lo hago IListy 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 encantaría ver una forma más fácil de hacerlo.

Slauma avatar Apr 04 '2011 15:04 Slauma

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

En la composición, el objeto hijo se crea cuando se crea el padre y se destruye cuando se destruye su padre . Entonces su vida útil 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 el padre se destruye, el objeto hijo aún puede existir, ya que puede agregarse a un padre diferente más adelante. por ejemplo: la relación entre una lista de reproducción y las canciones de esa lista de reproducción. Si se elimina la lista de reproducción, las canciones no deberían eliminarse. Es posible que se agreguen 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 así 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 puedan asociarse más tarde con un padre diferente). Como 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 explícitamente. Y esto se puede hacer de forma más sencilla que las soluciones sugeridas anteriormente:

context.Children.RemoveRange(parent.Children);

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

parent.Children.Clear();
Mosh avatar Oct 07 '2015 03:10 Mosh