La relación no se pudo cambiar porque una o más de las propiedades de clave externa no admiten valores NULL
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();
Debes eliminar los elementos secundarios antiguos thisParent.ChildItems
uno 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 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 encantaría ver una forma más fácil de hacerlo.
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();