Giới thiệu ràng buộc FOREIGN KEY có thể gây ra chu kỳ hoặc nhiều đường dẫn tầng - tại sao?


295

Tôi đã vật lộn với điều này trong một thời gian và không thể hiểu được chuyện gì đang xảy ra. Tôi có một thực thể Thẻ chứa các Bên (thường là 2) - và cả Thẻ và Bên đều có Giai đoạn. Tôi đang sử dụng di chuyển Codefirst của EF và việc di chuyển không thành công với lỗi này:

Giới thiệu ràng buộc FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' trên bảng 'Sides' có thể gây ra chu kỳ hoặc nhiều đường dẫn tầng. Chỉ định TRÊN XÓA KHÔNG CÓ HÀNH ĐỘNG hoặc CẬP NHẬT KHÔNG CÓ HÀNH ĐỘNG, hoặc sửa đổi các ràng buộc khác NGOẠI TỪ.

Đây là thực thể Thẻ của tôi :

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

Đây là thực thể phụ của tôi :

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

}

Và đây là thực thể Giai đoạn của tôi :

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

Điều kỳ lạ là nếu tôi thêm phần sau vào lớp Giai đoạn của mình:

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

Việc di chuyển chạy thành công. Nếu tôi mở SSMS và nhìn vào các bảng, tôi có thể thấy rằng nó Stage_StageIdđã được thêm vào Cards(như mong đợi / mong muốn), tuy nhiên Sideskhông có tham chiếu đếnStage (không mong đợi).

Nếu tôi sau đó thêm

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

Đến lớp Side của tôi, tôi thấy StageIdcột được thêm vàoSide bảng .

Điều này đang hoạt động, nhưng bây giờ trong suốt ứng dụng của tôi, bất kỳ tài liệu tham khảo nào Stagecó chứa một SideId, trong một số trường hợp hoàn toàn không liên quan. Tôi muốn chỉ cung cấp cho tôi CardSidecác thực thể một thuộc Stagetính dựa trên lớp Giai đoạn trên mà không làm ô nhiễm lớp giai đoạn với các thuộc tính tham chiếu nếu có thể ... tôi đang làm gì sai?


7
Vô hiệu hóa xóa tầng bằng cách cho phép các giá trị null trong các tham chiếu ... vì vậy, trong SideClass thêm số nguyên Nullable và xóa [Required]thuộc tính =>public int? CardId { get; set; }
Jaider

2
Trong Lõi EF, Bạn nên vô hiệu hóa xóa tầng bằng DeleteBehavior.Restricthoặc DeleteBehavior.SetNull.
Sina Lotfi

Câu trả lời:


371

Stageđược yêu cầu , tất cả các mối quan hệ một-nhiều nơi Stagecó liên quan sẽ được xóa theo tầng theo mặc định. Nó có nghĩa là, nếu bạn xóa một Stagethực thể

  • việc xóa sẽ xếp tầng trực tiếp vào Side
  • các xóa sẽ thác trực tiếp Cardvà vì CardSidebắt buộc là một-nhiều mối quan hệ với Cascading xóa được kích hoạt theo mặc định một lần nữa sau đó nó sẽ thác từ CardđếnSide

Vì vậy, bạn có hai đường dẫn xóa tầng từ StageđếnSide - nguyên nhân gây ra ngoại lệ.

Bạn phải tạo Stagetùy chọn trong ít nhất một trong các thực thể (nghĩa là xóa [Required]thuộc tính khỏi Stagethuộc tính) hoặc vô hiệu hóa xóa tầng với API Fluent (không thể thực hiện với chú thích dữ liệu):

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

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

2
Cảm ơn Slauma. Nếu tôi sử dụng API trôi chảy như bạn đã trình bày ở trên, các trường khác có giữ lại hành vi xóa tầng của họ không? Tôi vẫn cần xóa Sides khi thẻ bị xóa, ví dụ.
SB2055

1
@ SB2055: Có, nó sẽ chỉ ảnh hưởng đến các mối quan hệ từ Stage. Các mối quan hệ khác vẫn không thay đổi.
Slauma

2
Có cách nào để biết các thuộc tính wich đang gây ra lỗi không? Tôi đang gặp vấn đề tương tự, và nhìn vào các lớp học của mình, tôi có thể thấy được chu kỳ ở đâu
Rodrigo Juarez

4
Đây có phải là một hạn chế trong việc thực hiện của họ? Có vẻ tốt đối với tôi để Stagexóa một tầng xuống Sidetrực tiếp và thông qua mộtCard
aaaaaa

1
Giả sử chúng ta đặt CascadeOnDelete thành false. Sau đó, chúng tôi đã xóa một bản ghi giai đoạn có liên quan đến một trong các bản ghi Thẻ. Điều gì xảy ra với Card.Stage (FK)? Nó vẫn giữ nguyên? hoặc nó được đặt thành Null?
ninbit 28/12/18

61

Tôi đã có một bảng có mối quan hệ vòng tròn với những người khác và tôi đã nhận được cùng một lỗi. Hóa ra đó là về khóa ngoại không thể rỗng. Nếu khóa không phải là null đối tượng liên quan phải được xóa và quan hệ tròn không cho phép điều đó. Vì vậy, sử dụng khóa ngoại không có giá trị.

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

5
Tôi đã xóa thẻ [Bắt buộc] nhưng điều quan trọng là sử dụng int?thay vì intđể nó không có giá trị.
VSB

1
Tôi đã thử nhiều cách khác nhau để tắt xóa tầng và không có gì hiệu quả - điều này đã khắc phục nó!
ambog36

5
Bạn không nên làm điều này nếu bạn không muốn cho phép Giai đoạn được đặt thành null (Giai đoạn là trường bắt buộc trong câu hỏi ban đầu).
cfwall

35

Bất cứ ai tự hỏi làm thế nào để làm điều đó trong lõi EF:

      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
Điều đó sẽ tắt xóa tầng trên tất cả các mối quan hệ. Xóa tầng có thể là một tính năng mong muốn cho một số trường hợp sử dụng.
Blaze

15
Ngoài ra,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Bánh quy

@ Bánh quy Hoặc các phương thức mở rộng đã thay đổi theo thời gian hoặc bạn quên builder _ .Entity<TEntity>() _trước đó HasOne() có thể được gọi là ...
ViRuSTriNiTy

1
@ViRuSTriNiTy, đoạn trích của tôi được 2 tuổi. Nhưng, tôi nghĩ bạn đã đúng - ngày nay sẽ là khi bạn chọn thực hiện IEntityTypeConfiguration<T>. Tôi không nhớ là đã thấy builder.Entity<T>phương pháp đó vào những ngày đó, nhưng tôi có thể sai. Tuy nhiên, cả hai sẽ hoạt động :)
Bánh quy

21

Tôi đã gặp lỗi này đối với nhiều thực thể khi tôi chuyển từ mô hình EF7 sang phiên bản EF6. Tôi không muốn phải đi qua từng thực thể một, vì vậy tôi đã sử dụng:

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

2
Điều này nên được thêm vào (các) lớp kế thừa từ DbContext, ví dụ như trong phương thức OnModelCreating. Trình xây dựng thuộc loại DbModelBuilder
CodingYourLife

Điều này làm việc cho tôi; .NET 4.7, EF 6. Một vấp ngã là tôi đã gặp lỗi, vì vậy khi tôi được tạo lại bằng tập lệnh di chuyển với các quy ước này đã bị xóa, nó đã KHÔNG PHẢI giúp đỡ. Chạy "Add-Migration" với "-Force" đã xóa tất cả và xây dựng lại nó bao gồm các quy ước ở trên. Vấn đề đã được giải quyết ...
James Joyce

Những cái đó không tồn tại trong lõi .net, có tương đương nào không?
jjxtra


20

Bạn có thể đặt cascadeDelete thành false hoặc true (trong phương thức di chuyển Up () của bạn). Phụ thuộc vào yêu cầu của bạn.

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

2
@Mussakkhir cảm ơn bạn đã trả lời. Cách của bạn rất thanh lịch và hơn thế nữa - nó chính xác hơn và nhắm trực tiếp đến vấn đề tôi gặp phải!
Nozim Turakulov

Chỉ cần đừng quên UPphương pháp có thể được sửa đổi bởi các hoạt động bên ngoài.
Mất trí nhớ

8

Trong .NET Core, tôi đã thay đổi tùy chọn onDelete thành ReferenceencialAction.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

Tôi cũng có vấn đề này, tôi đã giải quyết nó ngay lập tức với câu trả lời này từ một chủ đề tương tự

Trong trường hợp của tôi, tôi không muốn xóa bản ghi phụ thuộc vào việc xóa khóa. Nếu đây là trường hợp trong trường hợp của bạn, chỉ cần thay đổi giá trị Boolean trong quá trình di chuyển thành false:

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

Rất có thể, nếu bạn đang tạo các mối quan hệ gây ra lỗi trình biên dịch này nhưng DO muốn duy trì xóa tầng; bạn có một vấn đề với các mối quan hệ của bạn.


6

Tôi đã sửa cái này. Khi bạn thêm di chuyển, trong phương thức Up () sẽ có một dòng như thế này:

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

Nếu bạn chỉ xóa cascadeDelete từ cuối thì nó sẽ hoạt động.


5

Chỉ với mục đích tài liệu, với một người nào đó trong tương lai, điều này có thể được giải quyết đơn giản như thế này và với phương pháp này, bạn có thể thực hiện một phương pháp vô hiệu hóa một lần và bạn có thể truy cập phương pháp của mình một cách bình thường

Thêm phương thức này vào lớp cơ sở dữ liệu ngữ cảnh:

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

1

Điều này nghe có vẻ kỳ lạ và tôi không biết tại sao, nhưng trong trường hợp của tôi điều đó đã xảy ra vì ConnectionString của tôi đang sử dụng "." trong thuộc tính "nguồn dữ liệu". Khi tôi thay đổi nó thành "localhost", nó hoạt động như một bùa mê. Không có thay đổi khác là cần thiết.


1

Trong .NET Core tôi đã chơi với tất cả các câu trả lời trên - nhưng không có thành công nào. Tôi đã thực hiện các thay đổi rất nhiều trong cấu trúc DB và mỗi lần thêm di chuyển mới lại cố gắngupdate-database , nhưng lại nhận được cùng một lỗi.

Sau đó, tôi bắt đầu remove-migrationtừng cái một cho đến khi Gói quản lý gói ném cho tôi ngoại lệ:

Di chuyển '20170827183131 _ ***' đã được áp dụng cho cơ sở dữ liệu

Sau đó, tôi đã thêm di chuyển mới ( add-migration) và update-database thành công

Vì vậy, đề xuất của tôi sẽ là: xóa tất cả các lần di chuyển tạm thời của bạn, cho đến khi trạng thái DB hiện tại của bạn.


1

Các câu trả lời hiện tại rất tuyệt Tôi chỉ muốn thêm rằng tôi gặp phải lỗi này vì một lý do khác. Tôi muốn tạo một di chuyển EF ban đầu trên một DB hiện có nhưng tôi đã không sử dụng -IgnoreChanges cờ và áp dụng lệnh Cập nhật cơ sở dữ liệu trên một Cơ sở dữ liệu trống (cũng không thành công trên cơ sở dữ liệu).

Thay vào đó tôi đã phải chạy lệnh này khi cấu trúc db hiện tại là cấu trúc hiện tại:

Add-Migration Initial -IgnoreChanges

Có khả năng có một vấn đề thực sự trong cấu trúc db nhưng cứu thế giới từng bước một ...


1

Cách đơn giản là, Chỉnh sửa tập tin chuyển đổi của bạn (cascadeDelete: true)vào (cascadeDelete: false)sau đó sau khi gán lệnh Update-Cơ sở dữ liệu trong Package Manager Console.if của bạn đó là vấn đề di cư cuối cùng của bạn thì tất cả phải. Nếu không, hãy kiểm tra lịch sử di chuyển trước đó của bạn, sao chép những thứ đó, dán vào tệp di chuyển cuối cùng của bạn, sau đó làm điều tương tự. nó hoàn toàn làm việc cho tôi


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

Khi quá trình di chuyển của bạn không thành công, bạn sẽ được cung cấp một vài tùy chọn: 'Giới thiệu ràng buộc FOREIGN KEY' FK_dbo.RecommendsBook_dbo.Depidor_DepidorID 'trên bảng' Sách được đề xuất 'có thể gây ra chu kỳ hoặc nhiều đường dẫn tầng. Chỉ định TRÊN XÓA KHÔNG CÓ HÀNH ĐỘNG hoặc TRÊN CẬP NHẬT KHÔNG CÓ HÀNH ĐỘNG, hoặc sửa đổi các ràng buộc khác NGOẠI TỪ. Không thể tạo ràng buộc hoặc chỉ mục. Xem các lỗi trước đó. '

Dưới đây là một ví dụ về việc sử dụng 'sửa đổi các ràng buộc FOREIGN KEY khác' bằng cách đặt 'cascadeDelete' thành false trong tệp di chuyển và sau đó chạy 'update-database'.


0

Không có giải pháp nào nói trên làm việc cho tôi. Những gì tôi phải làm là sử dụng một int (int?) Nullable trên khóa ngoại không bắt buộc (hoặc không phải là khóa cột không null) và sau đó xóa một số di chuyển của tôi.

Bắt đầu bằng cách xóa các di chuyển, sau đó thử int nullable.

Vấn đề là cả sửa đổi và thiết kế mô hình. Không cần thay đổi mã.


-1

Làm cho các thuộc tính khóa ngoại của bạn thành nullable. Điều đó sẽ làm việc.


1
rằng câu trả lời trong các bình luận dưới các câu hỏi xin vui lòng giải thích ở đó
Kostia Mololkin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.