La introducción de la restricción FOREIGN KEY puede causar ciclos o múltiples rutas en cascada, ¿por qué?

295

He estado luchando con esto por un tiempo y no puedo entender qué está sucediendo. Tengo una entidad de tarjeta que contiene lados (generalmente 2), y tanto las cartas como los lados tienen un escenario. Estoy usando migraciones EF Codefirst y las migraciones fallan con este error:

La introducción de la restricción FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' en la tabla 'Sides' puede causar ciclos o múltiples rutas en cascada. Especifique ON DELETE NO ACTION o ON UPDATE NO ACTION, o modifique otras restricciones de FOREIGN KEY.

Aquí está mi entidad de tarjeta :

public class Card
{
    public Card()
    {
        Sides = new Collection<Side>();
        Stage = Stage.ONE;
    }

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    [ForeignKey("CardId")]
    public virtual ICollection<Side> Sides { get; set; }
}

Aquí está mi entidad secundaria :

public class Side
{
    public Side()
    {
        Stage = Stage.ONE;
    }

    [Key]
    [Required]     
    public virtual int SideId { get; set; } 

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    public int CardId { get; set; }

    [ForeignKey("CardId")]
    public virtual Card Card { get; set; }

}

Y aquí está mi entidad Stage :

public class Stage
{
    // Zero
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Ten seconds
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable<Stage> Values
    {
        get
        {
            yield return ONE;
            yield return TWO;
        }

    }

    public int StageId { get; set; }
    private readonly TimeSpan span;
    public string Title { get; set; }

    Stage(TimeSpan span, string title)
    {
        this.span = span;
        this.Title = title;
    }

    public TimeSpan Span { get { return span; } }
}

Lo extraño es que si agrego lo siguiente a mi clase Stage:

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }

La migración se ejecuta con éxito. Si abro SSMS y miro las tablas, puedo ver que Stage_StageIdse ha agregado a Cards(como se esperaba / desea), sin embargo, no Sidescontiene ninguna referencia a Stage(no se espera).

Si luego agrego

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }

Para mi clase Side, veo una StageIdcolumna agregada a mi Sidetabla.

Esto está funcionando, pero ahora en toda mi aplicación, cualquier referencia a Stagecontiene un SideId, que en algunos casos es totalmente irrelevante. Me gustaría darles a mis Cardy Sideentidades una Stagepropiedad basada en la clase Stage anterior sin contaminar la clase stage con propiedades de referencia si es posible ... ¿qué estoy haciendo mal?

SB2055
fuente
77
Deshabilite la eliminación en cascada permitiendo valores nulos en las referencias ... así que en la Sideclase agregue un entero anulable y elimine el [Required]atributo =>public int? CardId { get; set; }
Jaider el
2
En EF Core, debe deshabilitar la eliminación en cascada con DeleteBehavior.Restricto DeleteBehavior.SetNull.
Sina Lotfi

Respuestas:

371

Debido a que Stagees obligatorio , todas las relaciones de uno a muchos que estén Stageinvolucradas tendrán la eliminación en cascada habilitada de forma predeterminada. Significa que si elimina una Stageentidad

  • la eliminación en cascada directamente a Side
  • la eliminación caerá en cascada directamente a Cardy debido Cardy Sidetener una requerido uno-a-muchos relación con la eliminación en cascada activada por defecto de nuevo, a continuación, caerá en cascada a partir CarddeSide

Por lo tanto, tiene dos rutas de eliminación en cascada de Stagea Side, lo que provoca la excepción.

Debe hacer lo Stageopcional en al menos una de las entidades (es decir, eliminar el [Required]atributo de las Stagepropiedades) o deshabilitar la eliminación en cascada con Fluent API (no es posible con anotaciones de datos):

modelBuilder.Entity<Card>()
    .HasRequired(c => c.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Slauma
fuente
2
Gracias Slauma Si uso una API fluida como lo demostró anteriormente, ¿otros campos conservarán su comportamiento de eliminación en cascada? Todavía necesito que se eliminen los lados cuando se eliminan las tarjetas, por ejemplo.
SB2055
1
@ SB2055: Sí, solo afectará las relaciones de Stage. Otras relaciones permanecen sin cambios.
Slauma
2
¿Hay alguna forma de saber qué propiedades están causando el error? Tengo el mismo problema, y ​​mirando mis clases no puedo ver dónde está el ciclo
Rodrigo Juárez
44
¿Es esto una limitación en su implementación? Me parece bien para una Stageeliminación en cascada, Sidetanto directamente como a través de unCard
aaaaaa
1
Supongamos que establecemos CascadeOnDelete en falso. Luego eliminamos un registro de etapa que está relacionado con uno de los registros de la Tarjeta. ¿Qué le sucede a Card.Stage (FK)? ¿Sigue siendo el mismo? o se establece en Null?
ninbit
61

Tenía una tabla que tenía una relación circular con los demás y recibía el mismo error. Resulta que se trata de la clave externa que no era anulable. Si la clave no es anulable, el objeto relacionado debe eliminarse y las relaciones circulares no lo permiten. Por lo tanto, use una clave foránea anulable.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Cem Mutlu
fuente
55
Eliminé la etiqueta [Obligatoria], pero otra cosa importante era usarla en int?lugar de intdejarla anulable.
VSB
1
Intenté muchas formas diferentes de desactivar la eliminación en cascada y nada funcionó, ¡esto lo solucionó!
ambog36
55
No debe hacer esto si no desea permitir que Stage se establezca en nulo (Stage era un campo obligatorio en la pregunta original).
cfwall
35

Cualquiera que se pregunte cómo hacerlo en EF core:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
                {
                    relationship.DeleteBehavior = DeleteBehavior.Restrict;
                }
           ..... rest of the code.....
Nexus23
fuente
3
Eso desactivaría la eliminación en cascada en todas las relaciones. La eliminación en cascada puede ser una característica deseada para algunos casos de uso.
Blaze
15
Alternativamente,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Biscuits
@Biscuits O bien los métodos de extensión cambiaron con el tiempo o se olvidó de lo builder _ .Entity<TEntity>() _anterior, HasOne() se puede llamar ...
ViRuSTriNiTy
1
@ViRuSTriNiTy, mi fragmento tiene 2 años. Pero, creo que tienes razón, hoy en día sería para cuando optes por implementar IEntityTypeConfiguration<T>. No recuerdo haber visto el builder.Entity<T>método esos días, pero podría estar equivocado. Sin embargo, ambos funcionarán :)
Galletas
21

Recibí este error para muchas entidades cuando estaba migrando de un modelo EF7 a una versión EF6. No quería tener que pasar por cada entidad de a una por vez, así que solía:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Sean
fuente
2
Esto debe agregarse en las clases que heredan de DbContext, por ejemplo, en el método OnModelCreating. El constructor es del tipo DbModelBuilder
CodingYourLife
Esto funcionó para mí; .NET 4.7, EF 6. Un escollo fue que recibí el error, por lo que cuando regeneré mediante un script de migración con estas convenciones eliminadas, no apareció para ayudar. Ejecutar "Add-Migration" con "-Force" lo borró todo y lo reconstruyó incluyendo estas convenciones anteriores. Problema resuelto ...
James Joyce
Esos no existen en .net core, ¿hay algún equivalente allí?
jjxtra
20

Puede establecer cascadeDelete en falso o verdadero (en su método de migración Up ()). Depende de su requerimiento.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Musakkhir Sayyed
fuente
2
@Mussakkhir gracias por tu respuesta. Su camino es muy elegante y más terminado: ¡es más preciso y está dirigido directamente al problema que enfrento!
Nozim Turakulov
Simplemente no olvide que el UPmétodo podría ser modificado por operaciones externas.
Demencial
8

En .NET Core cambié la opción onDelete a ReferencialAction.NoAction

         constraints: table =>
            {
                table.PrimaryKey("PK_Schedule", x => x.Id);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_HomeId",
                    column: x => x.HomeId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_VisitorId",
                    column: x => x.VisitorId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
            });
Mike Jones
fuente
7

También tuve este problema, lo resolví al instante con esta respuesta de un hilo similar

En mi caso, no quería eliminar el registro dependiente en la eliminación de clave. Si este es el caso en su situación, simplemente cambie el valor booleano en la migración a falso:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

Lo más probable es que si está creando relaciones que arrojan este error de compilación pero SI desean mantener la eliminación en cascada; Tienes un problema con tus relaciones.

jonc.js
fuente
6

Arreglé esto. Cuando agrega la migración, en el método Up () habrá una línea como esta:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)

Si solo borras el cascadeDelete desde el final, funcionará.

Usman Khan
fuente
5

Solo para fines de documentación, para alguien que viene en el futuro, esto se puede resolver de una manera tan simple como esto, y con este método, podría hacer un método que se deshabilitó una vez, y podría acceder a su método normalmente

Agregue este método a la clase de base de datos de contexto:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
sgrysoft
fuente
1

Esto suena extraño y no sé por qué, pero en mi caso eso estaba sucediendo porque mi ConnectionString estaba usando "." en el atributo "fuente de datos". Una vez que lo cambié a "localhost" funcionó a las mil maravillas. No se necesitaba otro cambio.

Marco Alves
fuente
1

En .NET Core jugué con todas las respuestas superiores, pero sin ningún éxito. Hice muchos cambios en la estructura de la base de datos y cada vez agregué nuevas migraciones intentando update-database, pero recibí el mismo error.

Luego comencé remove-migrationuno por uno hasta que Package Manager Console me arrojó una excepción:

La migración '20170827183131 _ ***' ya se ha aplicado a la base de datos

Después de eso, agregué nueva migración ( add-migration) y con update-database éxito

Entonces, mi sugerencia sería: eliminar todas las migraciones temporales, hasta el estado actual de la base de datos.

caminante
fuente
1

Las respuestas existentes son geniales, solo quería agregar que me encontré con este error por una razón diferente. Quería crear una migración EF inicial en una base de datos existente pero no utilicé -IgnoreChanges indicador y apliqué el comando Actualizar-Base de datos en una Base de datos vacía (también en las existentes).

En cambio, tuve que ejecutar este comando cuando la estructura de db actual es la actual:

Add-Migration Initial -IgnoreChanges

Es probable que haya un problema real en la estructura de base de datos, pero salve al mundo paso a paso ...

CodingYourLife
fuente
1

La forma más sencilla es editar el archivo de migración (cascadeDelete: true)en el archivo (cascadeDelete: false)después de asignar el comando Actualizar base de datos en la Consola del Administrador de paquetes. Si hay algún problema con su última migración, está bien. De lo contrario, verifique su historial de migración anterior, copie esas cosas, péguelo en su último archivo de migración, luego haga lo mismo. A mí me funciona perfectamente.

Niroshan Kumarasamy
fuente
1
public partial class recommended_books : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.RecommendedBook",
            c => new
                {
                    RecommendedBookID = c.Int(nullable: false, identity: true),
                    CourseID = c.Int(nullable: false),
                    DepartmentID = c.Int(nullable: false),
                    Title = c.String(),
                    Author = c.String(),
                    PublicationDate = c.DateTime(nullable: false),
                })
            .PrimaryKey(t => t.RecommendedBookID)
            .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
            .Index(t => t.CourseID)
            .Index(t => t.DepartmentID);

    }

    public override void Down()
    {
        DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
        DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
        DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
        DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
        DropTable("dbo.RecommendedBook");
    }
}

Cuando su migración falla, se le dan un par de opciones: 'Introducción de la restricción FOREIGN KEY' FK_dbo.RecommendedBook_dbo.Department_DepartmentID 'en la tabla' RecommendedBook 'puede causar ciclos o múltiples rutas en cascada. Especifique ON DELETE NO ACTION o ON UPDATE NO ACTION, o modifique otras restricciones de FOREIGN KEY. No se pudo crear restricción o índice. Ver errores anteriores.

Aquí hay un ejemplo del uso de 'modificar otras restricciones de CLAVE EXTRANJERA' estableciendo 'cascadeDelete' en falso en el archivo de migración y luego ejecute 'update-database'.

Christopher Govender
fuente
0

Ninguna de las soluciones mencionadas funcionó para mí. Lo que tenía que hacer era usar un int anulable (int?) En la clave externa que no era necesaria (o no una clave de columna no nula) y luego eliminar algunas de mis migraciones.

Comience eliminando las migraciones, luego intente con int.

El problema fue tanto la modificación como el diseño del modelo. No fue necesario cambiar el código.

Ayson Baxter
fuente
-1

Haga que sus atributos de clave externa sean anulables. Que funcionará.

Umair Javed
fuente
1
que la respuesta en los comentarios bajo las preguntas por favor explique allí
Kostia Mololkin