8

我有一個.NET4.0應用與實體框架5.0 e Sql Server CE 4.0。刪除父母與子女之間的一對多關係

我有兩個具有一對多(父/子)關係的實體。我已經配置它級聯刪除父刪除,但由於某種原因,它似乎並沒有工作。

這裏是我的實體的簡化版本:

public class Account 
    { 
     public int AccountKey { get; set; } 
     public string Name { get; set; } 

     public ICollection<User> Users { get; set; } 
    } 

    internal class AccountMap : EntityTypeConfiguration<Account> 
    { 
     public AccountMap() 
     { 
      this.HasKey(e => e.AccountKey); 
      this.Property(e => e.AccountKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 
     } 
    } 


    public class User 
    { 
     public int UserKey { get; set; } 
     public string Name { get; set; } 

     public Account Account { get; set; } 
     public int AccountKey { get; set; } 
    } 

    internal class UserMap : EntityTypeConfiguration<User> 
    { 
     public UserMap() 
     { 
      this.HasKey(e => e.UserKey); 
      this.Property(e => e.UserKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 


      this.HasRequired(e => e.Account) 
       .WithMany(e => e.Users) 
       .HasForeignKey(e => e.AccountKey); 
     } 
    } 

    public class TestContext : DbContext 
    { 
     public TestContext() 
     { 
      this.Configuration.LazyLoadingEnabled = false; 
     } 

     public DbSet<User> Users { get; set; } 
     public DbSet<Account> Accounts { get; set; } 

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

    } 

連接字符串:

<connectionStrings> 
    <add name="TestContext" connectionString="Data Source=|DataDirectory|\TestDb.sdf;" providerName="System.Data.SqlServerCe.4.0" /> 
    </connectionStrings> 

而且我的應用程序的工作流程的簡化版本:

static void Main(string[] args) 
{ 
    try 
    { 
     Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>()); 
     using (var context = new TestContext()) 
      context.Database.Initialize(false); 

     Account account = null; 
     using (var context = new TestContext()) 
     { 
      var account1 = new Account() { Name = "Account1^" }; 
      var user1 = new User() { Name = "User1", Account = account1 }; 

      context.Accounts.Add(account1); 
      context.Users.Add(user1); 

      context.SaveChanges(); 

      account = account1; 
     } 

     using (var context = new TestContext()) 
     { 
      context.Entry(account).State = EntityState.Deleted; 
        context.SaveChanges(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.ToString()); 
    } 

    Console.WriteLine("\nPress any key to exit..."); 
    Console.ReadLine(); 
} 

當我嘗試刪除父實體,它拋出:

該關係無法更改,因爲 外鍵屬性中的一個或多個屬性不可爲空。當對 關係進行更改時,相關的外鍵屬性將設置爲空值。 如果外鍵不支持空值,則必須定義新的關係 ,必須爲外鍵屬性指定另一個非空值,或者必須刪除不相關的對象。

我相信我的關係配置沒問題(followed the documentation)。我也搜索了guidelines on deleting detached entities

我真的不明白爲什麼刪除不起作用。我想避免加載所有的孩子,一個一個刪除它們,刪除父母,因爲必須有更好的解決方案。

回答

12

將一個實體的狀態設置爲Deleted,並且對該實體調用DbSet<T>.Remove並不相同。

所不同的是,雖然Remove做到這一點,如果關係配置了級聯刪除設置狀態只改變根實體(你進入context.Entry之一)的狀態Deleted但不相關實體的狀態。

如果你得到一個異常實際上取決於孩子(全部或只有一部分)被附加到上下文或不。這導致了行爲,有些難以遵循:

  • 如果你打電話Remove你沒有得到一個異常,如果沒有孩子被加載與否的問題。還有一個區別:
    • 如果孩子被附加到上下文,EF會爲每一個連接的孩子DELETE語句,那麼父(因爲Remove做了標記他們都爲Deleted
    • 如果兒童不與上下文聯繫EF只會向父數據庫發送DELETE聲明,並且由於啓用了級聯刪除,數據庫也會刪除子項。
  • 如果設置了根實體的狀態Deleted可以可能得到一個例外:
    • 如果兒童被連接到他們的國家將不會被設置爲Deleted和EF將上下文抱怨你試圖以必需的關係刪除一個委託人(根實體),而不刪除依賴者(子女),或者至少不把它們的外鍵設置爲不處於Deleted狀態的另一個根實體。這就是你有例外:account是根user1是從屬的account,並呼籲context.Entry(account).State = EntityState.Deleted;狀態Unchanged也將被附加user1上下文(或更改檢測SaveChanges會做到這一點,我不知道該鄰接)。 user1account.Users集合的一部分,因爲關係修正會將其添加到第一個上下文中的集合中,儘管您沒有在代碼中明確添加它。
    • 如果上下文中沒有附加任何子設置,則根的狀態爲Deleted將向數據庫發送DELETE語句,並且數據庫中的級聯刪除也將刪除子項。這毫無例外地運作。例如,如果在第二個上下文中將狀態設置爲Deleted之前或者在輸入第二個上下文之前設置了account.Users = null,那麼代碼將起作用。

在使用Remove我的意見了......

using (var context = new TestContext()) 
{ 
    context.Accounts.Attach(account); 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

...顯然是首選方式,因爲Remove的行爲更像是你所期望的用於級聯所需的關係刪除(您的模型就是這種情況)。手動狀態改變對其他實體狀態的依賴性使其更難以使用。我會認爲它只適用於特殊情況下的高級用法。

這種差異並不廣泛已知或記錄在案。我見過很少的文章。我現在能找到的唯一一個是this one by Zeeshan Hirani

+1

非常有啓發性,@Slauma!在發佈問題之前,我搜索了大量線索,但沒有找到您提到的帖子。謝謝 –

+0

似乎很多人在EF中嘗試使用父 - 子操作工作時遇到如此多的問題,比如插入,更新和刪除,每當我讀到更多關於EF的信息時,我就越失望。 –

1

我試過了一些不同的方法,奇怪的是,它工作。如果我將這段代碼:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Deleted; 
    context.SaveChanges(); 
} 

通過這一個:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Unchanged; 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

它的工作原理,沒有進一步的問題。不知道這是一個錯誤還是我錯過了一些東西。我真的很感謝這件事,因爲我很確定第一種方式(EntityState.Deleted)是推薦的方式。