410 Stimmen

Das Hinzufügen einer Fremdschlüssel-Einschränkung kann zu Zyklen oder mehrfachen Kaskadenpfaden führen - warum?

Ich habe eine Weile damit gerungen und kann nicht ganz herausfinden, was passiert. Ich habe eine Card-Entität, die Seiten (normalerweise 2) enthält - und sowohl Cards als auch Seiten haben eine Stage. Ich verwende EF Codefirst-Migrationen und die Migrationen schlagen mit diesem Fehler fehl:

Durch das Einführen des Fremdschlüsselconstraints 'FK_dbo.Sides_dbo.Cards_CardId' in der Tabelle 'Sides' können Zyklen oder mehrere Kaskadenpfade entstehen. Geben Sie ON DELETE NO ACTION oder ON UPDATE NO ACTION an oder ändern Sie andere Fremdschlüssel Constraints.

Hier ist meine Card-Entität:

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

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

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

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

Hier ist meine Side-Entität:

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

}

Und hier ist meine Stage-Entität:

public class Stage
{
    // Null
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Zehn Sekunden
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable 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; } }
}

Was seltsam ist, ist dass, wenn ich Folgendes meiner Stage-Klasse hinzufüge:

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

Die Migration erfolgreich durchgeführt wird. Wenn ich SSMS öffne und mir die Tabellen anschaue, sehe ich, dass Stage_StageId zu Cards hinzugefügt wurde (wie erwartet/gewünscht), jedoch enthält Sides keine Referenz zu Stage (nicht erwartet).

Wenn ich dann hinzufüge

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

Zu meiner Side-Klasse, sehe ich, dass die StageId-Spalte zu meiner Side-Tabelle hinzugefügt wurde.

Dies funktioniert, aber jetzt enthält in meiner Anwendung jede Referenz zu Stage eine SideId, die in einigen Fällen völlig irrelevant ist. Ich möchte meinen Card- und Side-Entitäten einfach eine Stage-Eigenschaft basierend auf obiger Stage-Klasse geben, ohne die Stage-Klasse mit Referenzeigenschaften zu verschmutzen, wenn möglich... was mache ich falsch?

2voto

Umair Javed Punkte 31

Machen Sie Ihre Foreign-Key-Attribute nullable. Das wird funktionieren.

2voto

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) // war in der Migration auf true gesetzt
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // war in der Migration auf true gesetzt
            .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");
    }
}

Wenn Ihre Migration fehlschlägt, haben Sie ein paar Optionen: 'Das Einführen des Fremdschlüsselconstraints 'FK_dbo.RecommendedBook_dbo.Department_DepartmentID' in der Tabelle 'RecommendedBook' kann Zyklen oder mehrere Kaskadenpfade verursachen. Geben Sie ON DELETE NO ACTION oder ON UPDATE NO ACTION an oder ändern Sie andere Fremdschlüsselconstraints. Konnte Constraint oder Index nicht erstellen. Siehe vorherige Fehler.'

Hier ein Beispiel für die Verwendung der 'Änderung anderer Fremdschlüsselconstraints', indem Sie 'cascadeDelete' in der Migrationsdatei auf false setzen und dann 'update-database' ausführen.

1voto

CodingYourLife Punkte 5512

Die vorhandenen Antworten sind großartig, ich wollte nur hinzufügen, dass ich auf diesen Fehler gestoßen bin, weil es einen anderen Grund gab. Ich wollte eine Initial EF-Migration in einer vorhandenen DB erstellen, aber ich habe die -IgnoreChanges Flag nicht verwendet und den Update-Database-Befehl auf einer leeren Datenbank angewendet (auch auf den vorhandenen fehlschlägt).

Stattdessen musste ich diesen Befehl ausführen, wenn die aktuelle db-Struktur die aktuelle ist:

Add-Migration Initial -IgnoreChanges

Es gibt wahrscheinlich ein echtes Problem in der Datenbankstruktur, aber rette die Welt Schritt für Schritt...

1voto

Ogglas Punkte 48648

In .NET 5 und .NET Core 2.0 können Sie .OnDelete(DeleteBehavior.Restrict) in OnModelCreating verwenden, wie @Nexus23 es beantwortet hat, aber Sie müssen den Cascade nicht für jedes Modell deaktivieren.

Beispiel mit Konfiguration des Verbindungsentitätstyps Many-to-Many:

interner Klasse MyContext : DbContext
{
    public MyContext(DbContextOptions options)
        : base(options)
    {
    }

    public DbSet Posts { get; set; }
    public DbSet Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId)
                    .OnDelete(DeleteBehavior.Restrict),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId)
                    .OnDelete(DeleteBehavior.Restrict),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection Tags { get; set; }
    public List PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection Posts { get; set; }
    public List PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

Quellen:

https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

Dies erfordert, dass Sie die Many-to-Many-Beziehung selbst entfernen, andernfalls erhalten Sie den folgenden Fehler, wenn Sie eine übergeordnete Entität entfernen:

Die Beziehung zwischen den Entitätstypen '' und '' wurde gekappt, aber die Beziehung ist entweder als erforderlich markiert oder implizit erforderlich, da der Fremdschlüssel nicht nullable ist. Wenn die abhängige/untergeordnete Entität gelöscht werden soll, wenn eine erforderliche Beziehung gekappt wird, konfigurieren Sie die Beziehung so, dass erzwungene Löschvorgänge ausgeführt werden. Verwenden Sie 'DbContextOptionsBuilder.EnableSensitiveDataLogging', um die Schlüsselwerte anzuzeigen

Dies können Sie lösen, indem Sie stattdessen DeleteBehavior.ClientCascade verwenden, was EF ermöglicht, Löschvorgänge auf geladenen Entitäten durchzuführen.

interner Klasse MyContext : DbContext
{
    public MyContext(DbContextOptions options)
        : base(options)
    {
    }

    public DbSet Posts { get; set; }
    public DbSet Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId)
                    .OnDelete(DeleteBehavior.Cascade),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId)
                    .OnDelete(DeleteBehavior.ClientCascade),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection Tags { get; set; }
    public List PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection Posts { get; set; }
    public List PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

1voto

Marco Alves Punkte 2696

Dies hört sich seltsam an und ich weiß nicht warum, aber in meinem Fall trat das auf, weil mein ConnectionString "." im Attribut "Datenquelle" verwendet hat. Sobald ich es auf "localhost" geändert habe, hat es wie ein Zauber funktioniert. Keine weitere Änderung war erforderlich.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X