การแนะนำข้อ จำกัด KEY ต่างประเทศอาจทำให้เกิดรอบหรือเส้นทางเรียงซ้อนหลายเส้นทาง - ทำไม?


295

ฉันต่อสู้กับสิ่งนี้มาระยะหนึ่งแล้วไม่สามารถเข้าใจได้ว่าเกิดอะไรขึ้น ฉันมีเอนทิตีการ์ดที่มีด้าน (ปกติ 2) - และทั้งการ์ดและด้านข้างมีสเตจ ฉันใช้การโยกย้าย EF Codefirst และการโยกย้ายล้มเหลวด้วยข้อผิดพลาดนี้:

การแนะนำข้อ จำกัด KEY ต่างประเทศ 'FK_dbo.Sides_dbo.Cards_CardId' บนตาราง 'ด้าน' อาจทำให้เกิดรอบหรือเส้นทางเรียงซ้อนหลายเส้นทาง ระบุ ON DELETE NO ACTION หรือ UPDATE NO ACTION หรือแก้ไขข้อ จำกัด ของกุญแจต่างประเทศ

นี่คือกิจการบัตรของฉัน:

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

นี่คือเอนทิตีข้างของฉัน:

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

}

และนี่คือเอนทิตีบนเวทีของฉัน:

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

มีอะไรแปลก ๆ คือถ้าฉันเพิ่มสิ่งต่อไปนี้ในคลาส Stage ของฉัน:

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

การโยกย้ายทำงานได้สำเร็จ ถ้าฉันเปิด SSMS และดูตารางฉันจะเห็นว่าStage_StageIdถูกเพิ่มลงในCards(ตามที่คาดหวัง / ต้องการ) อย่างไรก็ตามSidesไม่มีการอ้างอิงถึงStage(ไม่คาดหวัง)

ถ้าฉันเพิ่ม

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

ในคลาสของฉันฉันเห็นStageIdคอลัมน์เพิ่มลงในSideตาราง ของฉัน

สิ่งนี้ใช้งานได้ แต่ตอนนี้ตลอดทั้งแอปพลิเคชันของฉันการอ้างอิงใด ๆ ที่Stageมี a SideIdซึ่งในบางกรณีไม่เกี่ยวข้องทั้งหมด ฉันต้องการให้คุณสมบัติของฉันCardและSideเอนทิตี้ของฉันStageขึ้นอยู่กับคลาสของสเตจด้านบนโดยไม่ทำให้ชั้นสเตจมีคุณสมบัติอ้างอิงถ้าเป็นไปได้ ... ฉันทำอะไรผิด


7
ปิดใช้งานการลบแบบเรียงซ้อนโดยอนุญาตให้มีค่า Null ในการอ้างอิง ... ดังนั้นในSideClass เพิ่มจำนวนเต็ม Nullable และลบ[Required]attribute =>public int? CardId { get; set; }
Jaider

2
ใน EF หลักคุณควรปิดน้ำตกลบด้วยหรือDeleteBehavior.Restrict DeleteBehavior.SetNull
Sina Lotfi

คำตอบ:


371

เพราะStageจะต้องทุกความสัมพันธ์แบบหนึ่งต่อหลายคนที่Stageมีส่วนเกี่ยวข้องจะต้อง cascading ลบเปิดใช้โดยปริยาย หมายความว่าถ้าคุณลบStageเอนทิตี

  • การลบจะเรียงซ้อนโดยตรงกับ Side
  • การลบจะเรียงซ้อนโดยตรงไปยังCardและเนื่องจากCardและSideมีความสัมพันธ์แบบหนึ่งต่อหลายอย่างที่จำเป็นต้องมีการเปิดใช้งานการลบแบบเรียงซ้อนตามค่าเริ่มต้นอีกครั้งจากนั้นจะเรียงซ้อนจากCardเป็นSide

ดังนั้นคุณมีเส้นทางการลบแบบเรียงซ้อนสองเส้นทางจากStageไปSide- ซึ่งทำให้เกิดข้อยกเว้น

คุณต้องStageเลือกอย่างน้อยหนึ่งรายการ (เช่นลบ[Required]แอตทริบิวต์ออกจากStageคุณสมบัติ) หรือปิดใช้งานการลบแบบเรียงซ้อนด้วย Fluent API (เป็นไปไม่ได้ที่จะมีคำอธิบายประกอบข้อมูล):

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

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

2
ขอบคุณ Slauma หากฉันใช้ API ได้อย่างคล่องแคล่วตามที่คุณได้แสดงไว้ด้านบนฟิลด์อื่น ๆ จะยังคงรักษาลักษณะการลบที่เกี่ยวข้อง ฉันยังต้องการลบด้านเมื่อการ์ดถูกลบเช่น
SB2055

1
@ SB2055: ใช่มันจะส่งผลกระทบต่อความสัมพันธ์Stageเท่านั้น ความสัมพันธ์อื่น ๆ ยังคงไม่เปลี่ยนแปลง
Slauma

2
มีวิธีใดที่จะรู้ว่าคุณสมบัติใดที่ทำให้เกิดข้อผิดพลาด? ฉันมีปัญหาเดียวกันและมองไปที่ชั้นเรียนของฉันฉันไม่สามารถดูว่าวงจรอยู่ที่ไหน
Rodrigo Juarez

4
นี่เป็นข้อ จำกัด ในการใช้งานหรือไม่? ดูเหมือนว่าดีสำหรับฉันที่Stageจะลบน้ำตกSideทั้งทางตรงและผ่านCard
aaaaaa

1
สมมติว่าเราตั้งค่า CascadeOnDelete เป็นเท็จ จากนั้นเราลบเรคคอร์ดสเตจซึ่งเกี่ยวข้องกับเรคคอร์ดหนึ่งในการ์ด เกิดอะไรขึ้นกับ Card.Stage (FK) มันยังคงเหมือนเดิมหรือไม่ หรือตั้งเป็น Null?
Ninbit

61

ฉันมีตารางที่มีความสัมพันธ์แบบวงกลมกับผู้อื่นและฉันได้รับข้อผิดพลาดเดียวกัน ปรากฎว่ามันเกี่ยวกับรหัสต่างประเทศซึ่งไม่เป็นโมฆะ หากคีย์นั้นไม่ใช่วัตถุที่เกี่ยวข้องเป็นโมฆะจะต้องถูกลบทิ้งและความสัมพันธ์แบบวงกลมไม่อนุญาต ดังนั้นใช้คีย์ต่างประเทศ nullable

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

5
ฉันลบแท็ก [จำเป็น] แต่สิ่งที่สำคัญกว่าคือการใช้int?แทนที่จะintปล่อยให้เป็นโมฆะ
VSB

1
ฉันลองใช้หลายวิธีในการปิดการลบแบบเรียงซ้อนและไม่มีอะไรทำงาน - นี่เป็นการแก้ไข!
ambog36

5
คุณไม่ควรทำเช่นนี้หากคุณไม่ต้องการอนุญาตให้สเตจตั้งค่าเป็นโมฆะ (สเตจเป็นฟิลด์บังคับในคำถามเดิม)
cfwall

35

ทุกคนสงสัยว่าจะทำอย่างไรใน 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.....

3
ที่จะปิดการลบแบบเรียงซ้อนบนความสัมพันธ์ทั้งหมด การลบเรียงซ้อนอาจเป็นคุณสมบัติที่ต้องการสำหรับบางกรณีการใช้งาน
Blaze

15
อีกทางเลือกหนึ่งbuilder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
บิสกิต

@Biscuits ไม่ว่าวิธีการส่วนขยายจะเปลี่ยนไปตามเวลาหรือคุณลืมbuilder _ .Entity<TEntity>() _ก่อนหน้าHasOne() นี้สามารถเรียก ...
ViRuSTriNiTy

1
@ViRuSTriNiTy ตัวอย่างของฉันอายุ 2 ปี แต่ผมคิดว่าคุณกำลังขวา - IEntityTypeConfiguration<T>ปัจจุบันมันจะเป็นเมื่อคุณเลือกที่จะดำเนินการ ฉันจำไม่ได้ว่าได้เห็นbuilder.Entity<T>วิธีการในสมัยนั้น แต่ฉันอาจจะผิด อย่างไรก็ตามพวกเขาทั้งคู่จะทำงาน :)
ขนมปังกรอบ

21

ฉันได้รับข้อผิดพลาดนี้สำหรับเอนทิตีจำนวนมากเมื่อฉันย้ายจากโมเดล EF7 ไปเป็นรุ่น EF6 ฉันไม่ต้องการที่จะต้องผ่านแต่ละหน่วยงานทีละคนดังนั้นฉันใช้:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

2
ควรเพิ่มในคลาส (es) ที่สืบทอดมาจาก DbContext เช่นในวิธีการ OnModelCreating ตัวสร้างเป็นประเภท DbModelBuilder
CodingYourLife

สิ่งนี้ได้ผลสำหรับฉัน .NET 4.7, EF 6. ฉันพบข้อผิดพลาดบล็อกหนึ่งบล็อกหนึ่งดังนั้นเมื่อฉันสร้างสคริปต์การย้ายข้อมูลใหม่โดยลบข้อตกลงเหล่านี้ออกไปฉันไม่ได้ปรากฏขึ้นเพื่อช่วยเหลือ การรัน "Add-Migration" ด้วย "-Force" จะล้างทั้งหมดและสร้างขึ้นมาใหม่รวมถึงข้อกำหนดเหล่านี้ด้านบน แก้ปัญหาแล้ว ...
James Joyce

สิ่งเหล่านั้นไม่มีอยู่ใน. net core เทียบเท่าใด ๆ
jjxtra

@jjxtra ตรวจสอบstackoverflow.com/questions/46526230/…
Sean

20

คุณสามารถตั้งค่า cascadeDelete เป็นเท็จหรือจริง (ในวิธีการโยกย้าย Up ()) ขึ้นอยู่กับความต้องการของคุณ

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

2
@ Mussakkhir ขอบคุณสำหรับคำตอบของคุณ วิธีการของคุณสวยสง่าและมากกว่า - แม่นยำกว่าและตรงเป้าหมายกับปัญหาที่ฉันเผชิญ!
Nozim Turakulov

อย่าลืมUPวิธีอาจถูกแก้ไขโดยการดำเนินการจากภายนอก
Dementic

8

ใน. NET Core ฉันเปลี่ยนตัวเลือก onDelete เป็น 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);
            });

7

ฉันมีปัญหานี้ด้วยฉันแก้ไขมันทันทีด้วยคำตอบจากกระทู้ที่คล้ายกันนี้

ในกรณีของฉันฉันไม่ต้องการลบระเบียนที่ต้องพึ่งพาในการลบคีย์ หากเป็นกรณีนี้ในสถานการณ์ของคุณเพียงแค่เปลี่ยนค่าบูลีนในการย้ายข้อมูลเป็นเท็จ:

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

โอกาสที่จะเกิดขึ้นคือถ้าคุณกำลังสร้างความสัมพันธ์ซึ่งจะทำให้เกิดข้อผิดพลาดของคอมไพเลอร์นี้ แต่คุณต้องการที่จะทำการลบแบบต่อเนื่อง คุณมีปัญหากับความสัมพันธ์ของคุณ


6

ฉันแก้ไขสิ่งนี้ เมื่อคุณเพิ่มการย้ายข้อมูลในเมธอด Up () จะมีบรรทัดดังนี้:

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

หากคุณเพียงแค่ลบ casDelete ลบออกจากจุดสิ้นสุดมันจะทำงาน


5

สำหรับวัตถุประสงค์ของเอกสารสำหรับคนที่มาในอนาคตสิ่งนี้สามารถแก้ไขได้อย่างง่ายเช่นนี้และด้วยวิธีนี้คุณสามารถทำวิธีที่ปิดการใช้งานได้เพียงครั้งเดียวและคุณสามารถเข้าถึงวิธีการของคุณได้ตามปกติ

เพิ่มวิธีนี้ในคลาสฐานข้อมูลบริบท:

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

1

นี่ฟังดูแปลกและฉันไม่รู้ว่าทำไม แต่ในกรณีของฉันที่เกิดขึ้นเพราะ ConnectionString ของฉันใช้ "" ในแอตทริบิวต์ "แหล่งข้อมูล" เมื่อฉันเปลี่ยนเป็น "localhost" มันใช้งานได้ดีเหมือนมีเสน่ห์ ไม่ต้องการการเปลี่ยนแปลงอื่น ๆ


1

ใน. NET Coreฉันเล่นกับคำตอบด้านบนทั้งหมด - แต่ไม่ประสบความสำเร็จ ฉันทำการเปลี่ยนแปลงจำนวนมากในโครงสร้าง DB และทุกครั้งที่มีการเพิ่มการย้ายข้อมูลใหม่พยายามupdate-databaseแต่ได้รับข้อผิดพลาดเดียวกัน

จากนั้นฉันเริ่มremove-migrationทีละตัวจนกระทั่งPackage Manager Consoleทำให้ฉันมีข้อยกเว้น:

การย้ายข้อมูล '20170827183131 _ ***' ได้ถูกนำไปใช้กับฐานข้อมูลแล้ว

หลังจากนั้นฉันเพิ่มการโยกย้ายใหม่ ( add-migration) และupdate-database สำเร็จ

ดังนั้นข้อเสนอแนะของฉันคือ: ล้างการย้ายชั่วคราวทั้งหมดของคุณจนกว่าสถานะฐานข้อมูลปัจจุบันของคุณ


1

คำตอบที่มีอยู่นั้นยอดเยี่ยมฉันแค่อยากจะเพิ่มว่าฉันพบข้อผิดพลาดนี้เพราะเหตุผลอื่น ฉันต้องการสร้างการโยกย้ายเริ่มต้นของ EF บนฐานข้อมูลที่มีอยู่ แต่ฉันไม่ได้ใช้แฟล็ก -IgnoreChangesและใช้คำสั่ง Update-Database บนฐานข้อมูลเปล่า (เช่นเดียวกับความล้มเหลวที่มีอยู่)

แต่ฉันต้องเรียกใช้คำสั่งนี้เมื่อโครงสร้าง db ปัจจุบันเป็นหนึ่งในปัจจุบัน:

Add-Migration Initial -IgnoreChanges

อาจเป็นปัญหาจริงในโครงสร้าง db แต่ช่วยโลกทีละขั้นตอน ...


1

วิธีง่ายๆคือการแก้ไขไฟล์การย้ายถิ่นของคุณ(cascadeDelete: true)เข้า(cascadeDelete: false)แล้วหลังจากกำหนดคำสั่งปรับปรุงฐานข้อมูลในการจัดการปัญหาแพคเกจ Console.if มันของคุณด้วยการโยกย้ายครั้งสุดท้ายของคุณแล้วคลิกขวาทั้งหมด มิฉะนั้นตรวจสอบประวัติการย้ายข้อมูลก่อนหน้าของคุณคัดลอกสิ่งเหล่านั้นวางลงในไฟล์การโยกย้ายครั้งล่าสุดหลังจากนั้นทำแบบเดียวกัน มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน


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

เมื่อการโยกย้ายของคุณล้มเหลวคุณจะได้รับตัวเลือกสองสามตัว: 'แนะนำข้อ จำกัด ของคีย์ต่างประเทศ' FK_dbo.RecommendedBook_dbo.Department_DepartmentID 'บนตาราง' RecommendedBook 'อาจทำให้เกิดรอบหรือเส้นทางเรียงซ้อนหลายเส้นทาง ระบุ ON DELETE NO ACTION หรือ UPDATE NO ACTION หรือแก้ไขข้อ จำกัด ของกุญแจต่างประเทศ ไม่สามารถสร้างข้อ จำกัด หรือดัชนี ดูข้อผิดพลาดก่อนหน้า '

นี่คือตัวอย่างของการใช้ 'แก้ไขข้อ จำกัด FOREIGN KEY อื่น ๆ ' โดยการตั้งค่า 'cascadeDelete' เป็นเท็จในไฟล์การโยกย้ายแล้วเรียกใช้ 'update-database'


0

การแก้ปัญหาดังกล่าวไม่ได้ผลสำหรับฉัน สิ่งที่ฉันต้องทำคือใช้ int ที่เป็นโมฆะได้ (int?) ใน foreign key ที่ไม่ต้องการ (หรือไม่ใช่คีย์คอลัมน์ที่ไม่มีค่า null) จากนั้นลบการย้ายข้อมูลบางส่วนของฉัน

เริ่มต้นด้วยการลบการย้ายข้อมูลจากนั้นลอง int ที่เป็นโมฆะ

ปัญหาคือทั้งการดัดแปลงและการออกแบบโมเดล ไม่จำเป็นต้องเปลี่ยนรหัส


-1

ทำให้แอตทริบิวต์คีย์ต่างประเทศของคุณเป็นโมฆะ ที่จะได้ผล


1
คำตอบในความคิดเห็นภายใต้คำถามโปรดอธิบายรายละเอียดที่นั่น
Kostia Mololkin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.