2013-05-09 27 views
13

Tôi có một bảng có tham chiếu tự trong đó ParentId là một FK đến ID (PK).
Sử dụng EF (mã đầu tiên), tôi đã thiết lập mối quan hệ của tôi như sau:Câu lệnh DELETE mâu thuẫn với ràng buộc TABLE REFERENCE với khung Entity

this.HasOptional(t => t.ParentValue) 
    .WithMany(t => t.ChildValues) 
    .HasForeignKey(t => t.ParentId); 

Khi tôi cố gắng để xóa các trẻ em và cha mẹ của nó, các lệnh DELETE vấn đề EF cơ sở dữ liệu không nằm trong thứ tự tôi mong đợi họ đi - nó cố gắng xóa bản ghi gốc đầu tiên.

Tôi nhận ra rằng tôi có một vài tùy chọn ở đây (không phải trong đó tôi thích):

  1. Xóa hồ sơ đứa con đầu lòng, làm đầy đủ lưu/cam kết, và sau đó xóa kỷ lục mẹ. Với sự phức tạp của mô hình của tôi và logic duy trì nó, đây không phải là một lựa chọn - không thể đưa ra nhiều lệnh commit bất cứ khi nào tôi muốn.
  2. Hòa tan mối quan hệ trước khi xóa mọi thứ. Điều này có vẻ giống như một giải pháp hợp lý hơn, nhưng một lần nữa, tôi phải đưa ra cam kết riêng biệt với một câu lệnh UPDATE trước DELETE. Tôi muốn tránh nhiều cuộc gọi lưu/cam kết.
  3. Sử dụng trình kích hoạt để xóa trẻ em trước khi xóa hồ sơ gốc. Nhưng tôi muốn tránh những yếu tố gây nên vấn đề và bản chất của họ càng nhiều càng tốt.

Vì vậy, câu hỏi là .. có cách nào để thực thi việc xóa trẻ em trước hồ sơ gốc không? Có lẽ tôi đang thiếu một số cách rõ ràng để nói với EF rằng nó cần phải chăm sóc những đứa trẻ này trước cha mẹ? Có thể có cách để chỉ dẫn EF xóa theo thứ tự ID giảm dần? Tôi không biết .. suy nghĩ?

+1

Bạn có thể sử dụng một thác xóa đây? Nếu bạn đang sử dụng mã di chuyển đầu tiên, bạn có thể đặt cascade = true khi bảng được tạo, nếu không bạn có thể phải cập nhật nó thông qua di chuyển khác hoặc thông qua cơ sở dữ liệu. Tôi nghĩ EF sẽ xử lý nếu nó có thể xếp hàng. –

+0

Ý tưởng hay. Các DBA của chúng tôi không muốn kích hoạt thác cho toàn bộ DB, nhưng chúng ta có thể thực hiện nó ngay trên tham chiếu FK này. Đó là những gì tôi vừa thảo luận với một DBA. :) Mặc dù tôi vẫn đang tìm kiếm các đề xuất/ý tưởng khác có lẽ nằm trong phạm vi mã liên quan đến EF. Cập nhật – Kon

+0

: Không thể thêm thác khi xóa vào FK trong tham chiếu tự trong bảng, do lỗi chu trình. – Kon

Trả lời

12

Xóa cha mẹ và con như sau không hiệu quả đối với tôi. Những đứa trẻ được xóa trước phụ huynh và nó là một khứ hồi đơn cơ sở dữ liệu (một cuộc gọi đến SaveChanges) với tất nhiên ba tuyên bố DELETE trong một giao dịch duy nhất:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault(); 

    foreach (var child in parent.Children.ToList()) 
     ctx.MyEntities.Remove(child); 

    ctx.MyEntities.Remove(parent); 

    ctx.SaveChanges(); 
} 

(Sử dụng ToList() là cần thiết ở đây vì gọi Remove cho những đứa trẻ cũng loại bỏ từ Children bộ sưu tập của cha mẹ. Nếu không sử dụng ToList một ngoại lệ thời gian chạy sẽ được ném ra rằng bộ sưu tập foreach vòng lặp được lặp lại trên đã được sửa đổi.)

thứ tự mà Remove được gọi cho trẻ em và cha mẹ không quan trọng. Điều này cũng hoạt động:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault(); 

    var children = parent.Children.ToList(); 

    ctx.MyEntities.Remove(parent); 

    foreach (var child in children) 
     ctx.MyEntities.Remove(child); 

    ctx.SaveChanges(); 
} 

EF sắp xếp các câu lệnh DELETE theo đúng thứ tự trong cả hai trường hợp.

Chương trình thử nghiệm đầy đủ (EF 5/.NET 4.5/SQL Server):

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 

namespace EFSelfReference 
{ 
    public class MyEntity 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 

     public int? ParentId { get; set; } 
     public MyEntity Parent { get; set; } 

     public ICollection<MyEntity> Children { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<MyEntity> MyEntities { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Entity<MyEntity>() 
       .HasOptional(e => e.Parent) 
       .WithMany(e => e.Children) 
       .HasForeignKey(e => e.ParentId); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Database.SetInitializer<MyContext>(
       new DropCreateDatabaseAlways<MyContext>()); 
      using (var ctx = new MyContext()) 
      { 
       ctx.Database.Initialize(false); 

       var parent = new MyEntity { Name = "Parent", 
        Children = new List<MyEntity>() }; 

       parent.Children.Add(new MyEntity { Name = "Child 1" }); 
       parent.Children.Add(new MyEntity { Name = "Child 2" }); 

       ctx.MyEntities.Add(parent); 

       ctx.SaveChanges(); 
      } 

      using (var ctx = new MyContext()) 
      { 
       var parent = ctx.MyEntities.Include(e => e.Children) 
        .FirstOrDefault(); 

       foreach (var child in parent.Children.ToList()) 
        ctx.MyEntities.Remove(child); 

       ctx.MyEntities.Remove(parent); 

       ctx.SaveChanges(); 
      } 
     } 
    } 
} 

Ảnh chụp màn hình sau khi using khối đầu tiên với nội dung hiện tại trong bảng DB trước khi các đối tượng được xóa:

screen 1

Ảnh chụp màn hình từ SQL Profiler sau khi người cuối cùng SaveChanges:

screen 2

Tức là Child 1 (Id = 2) và Child 2 (Id = 3) bị xóa trướcParent (Id = 1).

+0

nếu tôi không muốn xóa phụ huynh thì sao? Tôi chỉ muốn xóa đứa trẻ? – eugenekgn

1

Có một cách khác, (suy nghĩ về việc rút thăm trước khi thực hiện ...) bạn có thể đặt mối quan hệ là ON DELETE CASCADE và cố gắng xóa chỉ hàng gốc.

+4

Bạn không thể xóa tầng trên một ràng buộc TABLE REFERENCE, như được mô tả trong tiêu đề của câu hỏi. –

16

Tôi nhận ra câu trả lời là một năm cũ, nhưng tôi thấy nó không đầy đủ. Trong tâm trí của tôi, một bảng tự tham khảo được sử dụng để đại diện cho một chiều sâu tùy ý.

Ví dụ, hãy xem xét cấu trúc sau:

/* 
* earth 
*  europe 
*   germany 
*   ireland 
*    belfast 
*    dublin 
*  south america 
*   brazil 
*    rio de janeiro 
*   chile 
*   argentina     
*    
*/ 

Câu trả lời không giải quyết làm thế nào để xóa trái đất, hoặc châu Âu, từ cấu trúc bên trên.

Tôi gửi mã sau đây làm phương án thay thế (sửa đổi câu trả lời được cung cấp bởi Slauma, người đã làm tốt công việc btw).

Trong lớp MyContext, thêm các phương pháp sau:

public void DeleteMyEntity(MyEntity entity) 
{ 
    var target = MyEntities 
     .Include(x => x.Children) 
     .FirstOrDefault(x => x.Id == entity.Id); 

    RecursiveDelete(target); 

    SaveChanges(); 

} 

private void RecursiveDelete(MyEntity parent) 
{ 
    if (parent.Children != null) 
    { 
     var children = MyEntities 
      .Include(x => x.Children) 
      .Where(x => x.ParentId == parent.Id); 

     foreach (var child in children) 
     { 
      RecursiveDelete(child); 
     } 
    } 

    MyEntities.Remove(parent); 
} 

tôi cư dữ liệu bằng mã đầu tiên với lớp sau:

public class TestObjectGraph 
{ 
    public MyEntity RootEntity() 
    { 
     var root = new MyEntity 
     { 
      Name = "Earth", 
      Children = 
       new List<MyEntity> 
        { 
         new MyEntity 
         { 
          Name = "Europe", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity {Name = "Germany"}, 
            new MyEntity 
            { 
             Name = "Ireland", 
             Children = 
              new List<MyEntity> 
              { 
               new MyEntity {Name = "Dublin"}, 
               new MyEntity {Name = "Belfast"} 
              } 
            } 
           } 
         }, 
         new MyEntity 
         { 
          Name = "South America", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity 
            { 
             Name = "Brazil", 
             Children = new List<MyEntity> 
             { 
              new MyEntity {Name = "Rio de Janeiro"} 
             } 
            }, 
            new MyEntity {Name = "Chile"}, 
            new MyEntity {Name = "Argentina"} 
           } 
         } 
        } 
     }; 

     return root; 
    } 
} 

mà tôi lưu vào cơ sở dữ liệu của tôi với những điều sau đây mã:

ctx.MyEntities.Add(new TestObjectGraph().RootEntity()); 

sau đó gọi các lần xóa như sau:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities 
     .Include(e => e.Children) 
     .FirstOrDefault(); 

    var deleteme = parent.Children.First(); 

    ctx.DeleteMyEntity(deleteme); 
} 

mà kết quả trong cơ sở dữ liệu của tôi bây giờ có một cấu trúc như sau:

/* 
* earth 
*  south america 
*   brazil 
*    rio de janeiro 
*   chile 
*   argentina     
*    
*/ 

nơi europe và tất cả các con của nó sẽ bị xóa.

ở trên, tôi chỉ định con đầu tiên của nút gốc, để chứng minh rằng sử dụng mã của tôi, bạn có thể đệ quy xóa một nút và tất cả các con của nó từ bất kỳ đâu trong cấu trúc phân cấp.

nếu bạn muốn kiểm tra xóa everyting, bạn chỉ có thể sửa đổi dòng như thế này:

ctx.DeleteMyEntity(parent); 

hoặc bất cứ nút bạn muốn trong cây.

rõ ràng, tôi sẽ không nhận được tiền thưởng, nhưng hy vọng bài đăng của tôi sẽ giúp ai đó tìm kiếm giải pháp hoạt động cho các thực thể tự tham chiếu có chiều sâu tùy ý.

Đây là nguồn gốc đầy đủ, mà là một phiên bản sửa đổi của mã Slauma từ câu trả lời chọn:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 

namespace EFSelfReference 
{ 
    public class MyEntity 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 

     public int? ParentId { get; set; } 
     public MyEntity Parent { get; set; } 

     public ICollection<MyEntity> Children { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<MyEntity> MyEntities { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Entity<MyEntity>() 
       .HasOptional(e => e.Parent) 
       .WithMany(e => e.Children) 
       .HasForeignKey(e => e.ParentId); 
     } 


     public void DeleteMyEntity(MyEntity entity) 
     { 
      var target = MyEntities 
       .Include(x => x.Children) 
       .FirstOrDefault(x => x.Id == entity.Id); 

      RecursiveDelete(target); 

      SaveChanges(); 

     } 

     private void RecursiveDelete(MyEntity parent) 
     { 
      if (parent.Children != null) 
      { 
       var children = MyEntities 
        .Include(x => x.Children) 
        .Where(x => x.ParentId == parent.Id); 

       foreach (var child in children) 
       { 
        RecursiveDelete(child); 
       } 
      } 

      MyEntities.Remove(parent); 
     } 
    } 

    public class TestObjectGraph 
    { 
     public MyEntity RootEntity() 
     { 
      var root = new MyEntity 
      { 
       Name = "Earth", 
       Children = 
        new List<MyEntity> 
        { 
         new MyEntity 
         { 
          Name = "Europe", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity {Name = "Germany"}, 
            new MyEntity 
            { 
             Name = "Ireland", 
             Children = 
              new List<MyEntity> 
              { 
               new MyEntity {Name = "Dublin"}, 
               new MyEntity {Name = "Belfast"} 
              } 
            } 
           } 
         }, 
         new MyEntity 
         { 
          Name = "South America", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity 
            { 
             Name = "Brazil", 
             Children = new List<MyEntity> 
             { 
              new MyEntity {Name = "Rio de Janeiro"} 
             } 
            }, 
            new MyEntity {Name = "Chile"}, 
            new MyEntity {Name = "Argentina"} 
           } 
         } 
        } 
      }; 

      return root; 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Database.SetInitializer<MyContext>(
       new DropCreateDatabaseAlways<MyContext>()); 
      using (var ctx = new MyContext()) 
      { 
       ctx.Database.Initialize(false); 

       ctx.MyEntities.Add(new TestObjectGraph().RootEntity()); 
       ctx.SaveChanges(); 
      } 

      using (var ctx = new MyContext()) 
      { 
       var parent = ctx.MyEntities 
        .Include(e => e.Children) 
        .FirstOrDefault(); 

       var deleteme = parent.Children.First(); 

       ctx.DeleteMyEntity(deleteme); 
      } 

      Console.WriteLine("Completed...."); 
      Console.WriteLine("Press any key to exit"); 
      Console.ReadKey(); 
     } 
    } 
} 
Các vấn đề liên quan