La relación no se pudo cambiar porque una o más de las propiedades de clave externa no son anulables

192

Recibo este error cuando obtengo GetById () en una entidad y luego establezco 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 son anulables. 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 esta línea:

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

¿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 la simple asignación de clases secundarias modificadas en una colección a la clase primaria existente. Con suerte, esto atenderá la eliminación de las clases secundarias, la adición de nuevas y modificaciones. Pensé que Entity Framework maneja esto.

Las líneas de código se pueden destilar para:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
jaffa
fuente
Encontré mi respuesta comprar usando la solución n. ° 2 en el siguiente artículo, básicamente creé una clave principal agregada a la tabla secundaria para la referencia a la tabla primaria (por lo que tiene 2 claves principales (la clave externa para la tabla primaria y la ID) para la tabla secundaria). c-sharpcorner.com/UploadFile/ff2f08/…
yougotiger
@jaffa, encontré mi respuesta aquí stackoverflow.com/questions/22858491/…
antonio

Respuestas:

159

Debe eliminar los elementos secundarios antiguos thisParent.ChildItemsuno por uno manualmente. Entity Framework no hace eso por ti. Finalmente, no puede decidir qué desea hacer con los elementos secundarios antiguos: si desea tirarlos 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

Lo que 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. (Normalmente lo tengo IListy luego el código se ve un poco diferente). También eliminé todas las abstracciones del repositorio para simplificarlo.

No sé si es una buena solución, pero creo que se debe hacer algún tipo de trabajo duro en este sentido para hacer frente a todo tipo de cambios en la colección de navegación. También estaría feliz de ver una manera más fácil de hacerlo.

Slauma
fuente
Entonces, ¿qué pasa si algunos solo se cambian? ¿Eso significa que todavía tengo que eliminarlos y agregarlos nuevamente?
jaffa
@ Jon: No, también puede actualizar elementos existentes, por supuesto. Agregué un ejemplo de cómo probablemente actualizaría la colección secundaria, vea la sección Editar arriba.
Slauma
@Slauma: Lol, si supiera que vas a modificar tu respuesta, no escribiría mi respuesta ...
Ladislav Mrnka
@Ladislav: No, no, me alegra que hayas escrito tu propia respuesta. Ahora al menos sé que no es una tontería completa y demasiado complicado lo que hice anteriormente.
Slauma
1
Agregaría una condición al recuperar el elemento original del niño en el foreach: ... Donde (c => c.ID == childItem.ID && c.ID! = 0) de lo contrario, devolverá los hijos recién agregados si el childItem.ID == 0.
perfect_element
116

La razón por la que enfrenta 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 útil es 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 la 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 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 deberían eliminarse. Se pueden agregar 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 hijo 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 la 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 ha configurado su clave principal en su tabla secundaria. Debe ser compuesto, pero no lo es. Por lo tanto, Entity Framework ve esta asociación como agregación, lo que significa que, cuando elimina o borra los objetos secundarios, no va a 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 buena razón para no querer usar una clave compuesta, debe eliminar los objetos secundarios explícitamente. Y esto puede hacerse más simple que las soluciones sugeridas anteriormente:

context.Children.RemoveRange(parent.Children);

2- De lo contrario, al establecer la clave primaria adecuada en su tabla secundaria, su código tendrá más significado:

parent.Children.Clear();
Mosh
fuente
9
Encontré esta explicación muy útil.
Booji Boy
77
Buena explicación para composición vs agregación y cómo se relaciona el marco de la entidad.
Crisálida
# 1 fue la menor cantidad de código necesaria para solucionar el problema. ¡Gracias!
ryanulit
73

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

  • Carga Parentdesde la base de datos y obtiene una entidad adjunta
  • Reemplaza su colección infantil con una nueva colección de niños separados
  • Guarda los cambios, pero durante esta operación, todos los elementos secundarios se consideran agregados porque EF no los conocía hasta este momento. Por lo tanto, EF intenta establecer nulo en clave externa de los elementos antiguos e insertar todos los elementos secundarios nuevos => filas duplicadas.

Ahora 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 usar UpdateModel o TryUpdateModel .

Si solo desea actualizar los hijos 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 Modifiedtambién adjuntará la entidad) pero me gusta porque hace que el proceso sea más obvio.

Si desea modificar existente, eliminar 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();
Ladislav Mrnka
fuente
1
Pero hay un comentario interesante sobre el uso .Clone(). ¿Tiene en cuenta el caso de que a ChildItemtiene otras propiedades de navegación secundarias? Pero en ese caso, ¿no querríamos que todo el gráfico secundario se adjunte al contexto, ya que esperaríamos que todos los hijos secundarios sean objetos nuevos si el hijo en sí es nuevo? (Bueno, podría ser diferente de un modelo a otro, pero vamos a suponer el caso de que los sub-niño son "dependientes" del niño como los del niño dependen de los padres.)
Slauma
Probablemente requeriría un clon "inteligente".
Ladislav Mrnka
1
¿Qué pasa si no quieres tener una colección infantil en tu contexto? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed
1
parent.ChildItems.Remove (child); context.Childs.Remove (child); Este doble retiro solucionado puede emitir, GRACIAS. ¿Por qué necesitamos ambas eliminaciones? ¿Por qué eliminar solo de parent.ChildItems no es suficiente ya que childs solo vive como childs?
Fernando Torres
40

Encontré esta respuesta mucho más útil para el mismo error. Parece que a EF no le gusta cuando eliminas, prefiere Eliminar.

Puede eliminar una colección de registros adjuntos a un registro como este.

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

En el ejemplo, todos los registros detallados adjuntos a una orden tienen su estado establecido en Eliminar. (En preparación para Agregar Detalles actualizados nuevamente, como parte de una actualización de Pedido)

Greg Little
fuente
Creo que es la respuesta correcta.
desmati
Solución lógica y directa.
sairfan
19

¡No tengo idea de por qué las otras dos respuestas son tan populares!

Creo que tenía razón al suponer que el marco ORM debería manejarlo; después de todo, eso es lo que promete ofrecer. De lo contrario, su modelo de dominio se corrompe por problemas de persistencia. NHibernate maneja esto felizmente si configura la configuración de cascada correctamente. En Entity Framework también es posible, solo esperan que sigas mejores estándares al configurar tu modelo de base de datos, especialmente cuando tienen que inferir qué cascada se debe hacer:

Debe definir la relación padre-hijo correctamente utilizando una " relación de identificación ".

Si hace esto, Entity Framework sabe que el objeto secundario es identificado por el padre y, por lo tanto, debe ser una situación de "cascade-delete-huérfanos".

Aparte de lo anterior, es posible que necesite (de la experiencia NHibernate)

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

en lugar de reemplazar la lista por completo.

ACTUALIZAR

El comentario de @ Slauma me recordó que las entidades separadas son otra parte del problema general. Para resolver eso, puede utilizar un archivador de modelo personalizado que construya sus modelos intentando cargarlo desde el contexto. Esta publicación de blog muestra un ejemplo de lo que quiero decir.

Andre Luus
fuente
La configuración como relación de identificación no ayudará aquí porque el escenario en la pregunta tiene que ver con entidades separadas ( "mi nueva lista que proviene de la vista MVC" ). Todavía tiene que cargar los elementos secundarios originales de la base de datos, encontrar los elementos eliminados en esa colección en función de la colección separada y luego eliminarlos de la base de datos. La única diferencia es que con una relación de identificación puede llamar en parent.ChildItems.Removelugar de _dbContext.ChildItems.Remove. Todavía hay (EF <= 6) sin soporte incorporado de EF para evitar un código largo como el de las otras respuestas.
Slauma
Entiendo tu punto. Sin embargo, creo que con un cuaderno de modelo personalizado que carga la entidad desde el contexto o devuelve una nueva instancia, el enfoque anterior funcionaría. Actualizaré mi respuesta para sugerir esa solución.
Andre Luus
Sí, podría usar una carpeta modelo pero ahora tenía que hacer las cosas de las otras respuestas en la carpeta modelo. Simplemente mueve el problema de la capa de repositorio / servicio al modelo de carpeta. Al menos, no veo una simplificación real.
Slauma
La simplificación es la eliminación automática de entidades huérfanas. Todo lo que necesita en la carpeta del modelo es un equivalente genérico dereturn context.Items.Find(id) ?? new Item()
Andre Luus
Buena retroalimentación para el equipo de EF, pero su solución propuesta no resuelve nada en EF land desafortunadamente.
Chris Moschini
9

Si está utilizando AutoMapper con Entity Framework en la misma clase, puede encontrar este problema. Por ejemplo si tu clase es

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

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

Esto intentará copiar ambas propiedades. En este caso, ClassBId no es anulable. Como AutoMapper copiará, destination.ClassB = input.ClassB;esto causará un problema.

Configure su AutoMapper para Ignorar ClassBpropiedad.

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId
jsgoupil
fuente
Estoy enfrentando un problema similar con AutoMapper, pero esto no funciona para mí :( Ver stackoverflow.com/q/41430679/613605
J86
4

Acabo de tener el mismo error. Tengo dos tablas con una relación padre-hijo, pero configuré un "en cascada de eliminación" en la columna de clave externa en la definición de tabla de la tabla hijo. Entonces, cuando elimino manualmente la fila principal (a través de SQL) en la base de datos, eliminará automáticamente las filas secundarias.

Sin embargo, esto no funcionó en EF, apareció el error descrito en este hilo. La razón de esto fue que en mi modelo de datos de entidad (archivo edmx) las propiedades de la asociación entre la tabla primaria y la tabla secundaria no eran correctas. La End1 OnDeleteopción se configuró para ser none("End1" en mi modelo es el final que tiene una multiplicidad de 1).

Cambié manualmente la End1 OnDeleteopción Cascadey funcionó. No sé por qué EF no puede recoger esto cuando actualizo el modelo de la base de datos (tengo un primer modelo de base de datos).

Para completar, así es como se ve mi código para eliminar:

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

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

Si no tuviera una eliminación en cascada definida, tendría que eliminar las filas secundarias manualmente antes de eliminar la fila principal.

Martín
fuente
4

Esto sucede porque la entidad secundaria está marcada como modificada en lugar de eliminada.

Y la modificación que EF hace a la entidad secundaria cuando parent.Remove(child)se ejecuta, es simplemente establecer la referencia a su padre en null.

Puede verificar el EntityState del niño escribiendo el siguiente código en la Ventana Inmediata de Visual Studio cuando ocurra la excepción, después de ejecutar SaveChanges():

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

donde X debe ser reemplazado por la entidad eliminada.

Si no tiene acceso a la ObjectContextejecución _context.ChildEntity.Remove(child), puede resolver este problema haciendo que la clave externa sea parte de la clave primaria en la tabla secundaria.

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

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

De esta manera, si ejecuta parent.Remove(child), EF marcará correctamente la entidad como eliminada.

Mauricio Ramalho
fuente
2

Este tipo de solución me sirvió:

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 importante decir que esto elimina todos los registros y los inserta nuevamente. Pero para mi caso (menos de 10) está bien.

Espero que ayude.

Wagner Bertolini Junior
fuente
¿La reinserción ocurre con nuevas identificaciones o mantiene las identificaciones del niño que tenían en primer lugar?
Pepito Fernández
2

Me encontré con este problema hoy y quería compartir mi solución. En mi caso, la solución fue eliminar los elementos secundarios antes de obtener el elemento primario de la base de datos.

Anteriormente lo estaba haciendo como en el código a continuación. Entonces obtendré el mismo error listado en esta pregunta.

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

Lo que funcionó para mí es obtener primero los elementos secundarios, utilizando parentId (clave externa) y luego eliminar esos elementos. Entonces puedo obtener el padre de la base de datos y en ese punto, ya no debería tener ningún elemento secundario y puedo agregar nuevos elementos secundarios.

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

Debe borrar manualmente la colección ChildItems y agregar nuevos elementos en ella:

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

Después de eso, puede llamar al método de extensión DeleteOrphans que se manejará con entidades huérfanas (debe llamarse entre los métodos DetectChanges y SaveChanges).

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);
    }
}
Se sentó
fuente
Esto funcionó bien para mí. Solo necesitaba agregar context.DetectChanges();.
Andy Edinborough
1

He probado estas soluciones y muchas otras, pero ninguna de ellas funcionó. Como esta es la primera respuesta en Google, agregaré mi solución aquí.

El método que funcionó bien para mí fue eliminar las relaciones de la imagen durante los commits, por lo que EF no tenía nada que arruinar. Hice esto volviendo a encontrar el objeto principal en el DBContext y eliminándolo. Dado que las propiedades de navegación del objeto re-encontrado son todas nulas, las relaciones de los niños se ignoran durante la confirmación.

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

Tenga en cuenta que esto supone que las claves externas están configuradas con ON DELETE CASCADE, por lo que cuando se elimina la fila principal, la base de datos limpiará los elementos secundarios.

Steve
fuente
1

Solía solución de Mosh , pero no era obvio para mí la forma de aplicar la clave composición correctamente en código por primera vez.

Entonces aquí está la solución:

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; }
}
PeterB
fuente
1

Tuve el mismo problema, pero sabía que había funcionado bien en otros casos, así que reduje el problema a esto:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems tenía una clave primaria compuesta (parentId + alguna columna local) y funcionaba bien
  • ProblematicItems tenía su propia clave principal de una sola columna, y parentId era solo un FK. Esto estaba causando la excepción después de Clear ().

Todo lo que tenía que hacer era hacer que ParentId formara parte del PK compuesto para indicar que los niños no pueden existir sin un padre. Utilicé el modelo DB-first, agregué el PK y marqué la columna parentId como EntityKey (por lo tanto, tuve que actualizarlo tanto en DB como en EF, no estoy seguro de si EF sería suficiente).

Hice RequestId parte de la PK Y luego actualizó el modelo EF, Y configuró la otra propiedad como parte de la Clave de entidad

Una vez que lo piensa, es una distinción muy elegante que EF usa para decidir si los niños "tienen sentido" sin un padre (en este caso, Clear () no los eliminará y lanzará una excepción a menos que establezca ParentId en algo más / especial ), o, como en la pregunta original, esperamos que los elementos se eliminen una vez que se eliminen del elemento primario.

Ekus
fuente
0

Este problema surge porque tratamos de eliminar la tabla primaria y aún hay datos de la tabla secundaria. Resolvemos el problema con la ayuda de la eliminación en cascada.

En el modelo Crear método en la clase dbcontext.

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

Después de eso, en nuestra llamada API

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

La opción de eliminación en cascada elimina también la tabla secundaria relacionada con los padres con este código simple. Haz que lo intentes de esta manera simple.

Eliminar rango que se utiliza para eliminar la lista de registros en la base de datos Gracias

Sowmiya V
fuente
0

También resolví mi problema con la respuesta de Mosh y pensé en la respuesta de PeterB era un poco porque usaba una enumeración como clave foránea. Recuerde que deberá agregar una nueva migración después de agregar este código.

También puedo recomendar esta publicación de blog para otras soluciones:

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

Código:

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; }
}
Ogglas
fuente
0

Usando la solución de Slauma, creé algunas funciones genéricas para ayudar a actualizar objetos secundarios y colecciones de objetos secundarios.

Todos mis objetos persistentes implementan esta interfaz

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

Con esto implementé estas dos funciones en mi repositorio

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

Para usarlo hago lo siguiente:

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

Espero que esto ayude


EXTRA: También puede hacer una clase separada DbContextExtentions (o su propio contexto de contexto):

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

y úsalo como:

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);
Bluemoon74
fuente
También puede hacer una clase de extensión para su contexto con estas funciones:
Bluemoon74
0

Tuve el mismo problema cuando voy a eliminar mi registro que cuando ocurrió algún problema, porque esta solución de problema es que cuando va a eliminar su registro de lo que falta algo antes de eliminar el encabezado / registro maestro, debe escribir en el código para elimine sus detalles antes del encabezado / Master Espero que su problema se resuelva.

Ghazi Hur
fuente
-1

Me he encontrado con este problema antes de varias horas e intento todo, pero en mi caso la solución fue diferente de la que se menciona arriba.

Si usa una entidad ya recuperada de la base de datos e intenta modificarla para niños, se producirá el error, pero si obtiene una copia nueva de la entidad de la base de datos no debería haber ningún problema. No uses esto:

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

Utilizar este:

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