2013-05-09 79 views
13

我有一個帶有自引用的表,其中ParentId是ID(PK)的FK。
使用EF(代碼優先),我已經設置了我的關係如下:DELETE語句與實體框架中的SAME TABLE REFERENCE約束衝突

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

當我嘗試刪除該兒童及其父,該刪除命令EF問題數據庫不在我預計他們會去 - 它試圖首先刪除父記錄。

我意識到,我有幾個選擇這裏(均未我喜歡):第一

  1. 刪除子記錄,做一個完整的保存/提交,然後刪除父記錄。隨着我的模型和維護它的邏輯的複雜性,這不是一種選擇 - 無論何時都不能發出多個提交命令。
  2. 在刪除任何內容之前解散關係。這似乎是一個更明智的解決方案,但同樣,我必須在DELETE之前使用UPDATE語句發出單獨的提交。我想避免多次保存/提交調用。
  3. 在刪除父記錄之前使用觸發器刪除子項。但我想盡可能避免觸發器和它們的問題性質。

所以問題是..有沒有一種方法來強制在父記錄之前刪除子女?也許我錯過了某種明確的告訴EF的方式,即在父母之前需要照顧這些孩子?也許有一種方法可以指引EF以ID的降序刪除?我不知道..想法?

+1

你可以利用級聯刪除嗎?如果您使用的代碼先遷移,你可以設置= TRUE在創建表的級聯,否則你可能必須通過另一個遷移或通過數據庫進行更新。 我想EF會處理它,如果它能夠級聯。 – 2013-05-09 14:19:14

+0

好主意。我們的數據庫管理員不希望爲整個數據庫啓用級聯,但是我們可以在此FK參考中做到這一點。這正是我剛剛與DBA討論的內容。 :)儘管我仍在尋找其他建議/想法,可能在EF相關代碼的範圍內。 – Kon 2013-05-09 14:22:22

+0

更新:無法在刪除表中的自引用中添加級聯到FK,因爲週期誤差。 – Kon 2013-05-09 21:33:15

回答

12

像下面這樣刪除父母和孩子確實對我有用。父前的孩子被刪除,它是一個單一的數據庫往返(一次調用SaveChanges)在單一交易過程三個DELETE語句:

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

(使用ToList()有必要在這裏,因爲調用Remove孩子們還從父母的Children集合中刪除。不使用ToList運行時異常會被拋出,該foreach循環遍歷集合已經被修改。)

的順序Remove被稱爲孩子和家長無所謂。這也適用:

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在兩種情況下按正確的順序對DELETE語句進行排序。

完整的測試程序(EF 5/.NET 4。5/SQL服務器):第一using塊與數據庫表中的當前內容後

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

截圖之前,實體被刪除:

screen 1

從SQL事件探查器截圖最後SaveChanges後:

screen 2

iee Child 1(Id = 2)和Child 2(Id = 3)在Parent(Id = 1)之前被刪除

+0

如果我不想刪除父項,該怎麼辦?我只想刪除孩子? – eugenekgn 2015-06-11 20:20:19

1

還有另外一種方法,(在做這件事之前先考慮一下退縮......) 您可以將關係設置爲ON DELETE CASCADE,並嘗試刪除父行。

+4

你不能在同一個表引用約束刪除級聯,作爲問題的標題描述。 – 2015-07-22 23:00:25

16

我意識到答案是一歲,但我覺得它不完整。在我看來,使用自引用表來表示任意深度。

例如,請考慮以下結構:

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

答案沒有解決如何刪除地球,還是歐洲,從結構上。

我提交下面的代碼作爲替代(修改Slauma提供的答案,誰順利做了一個好工作)。

在MyContext類,添加以下方法:

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

我保存到我的數據庫具有以下代碼:

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

然後調用這樣的刪除:

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

    var deleteme = parent.Children.First(); 

    ctx.DeleteMyEntity(deleteme); 
} 

導致我的數據庫現在有像這樣的結構:

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

,其中歐洲和所有的孩子都被刪除。

在上面,我指定了根節點的第一個孩子,爲了演示如何使用我的代碼,您可以遞歸地從層次結構中的任何位置刪除一個節點及其所有子節點。

,如果你想測試刪除everyting,你可以簡單地修改這樣的行:你的樹要

ctx.DeleteMyEntity(parent); 

或任何節點。

很明顯,我不會得到賞金,但我希望我的帖子將幫助別人尋找一個任意深度的自引用實體有效的解決方案。

下面是完整的源代碼,這是從選定的答案Slauma的代碼修改後的版本:

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(); 
     } 
    } 
} 
相關問題