2017-01-25 208 views
2

我有一個SQLite數據庫與實體框架映射。 有2個表格:集合(1:n)專輯。實體框架硬級聯刪除

當我刪除一個集合時,所有相關的專輯也必須被刪除。 我使用CollectionRepo.Delete(collection);來實現這一點。它使用下面的代碼:

public int Delete(Collection entity) 
{ 
    Context.Entry(entity).State = EntityState.Deleted; 
    return Context.SaveChanges(); 
} 

的問題是:當我執行這個代碼,Context.SaveChanges();給我一個例外:

操作失敗:關係不能被改變,因爲一個或多個外鍵屬性是不可空的。當對關係進行更改時,相關的外鍵屬性將設置爲空值。如果外鍵不支持空值,則必須定義新的關係,必須爲外鍵屬性指定另一個非空值,或者必須刪除不相關的對象。

看來,實體框架想在外鍵上null而不是刪除條目。但這絕對不是我想要的,因爲沒有父母(至少在我的用例中),專輯沒有意義。

我明顯可以手動刪除相冊,然後刪除空集合,但在我看來有點棘手。首先,在我看來,EF應該足夠聰明,可以自己來簡化代碼;其次,如果我與收藏和專輯有很多關係,那麼我最終會面臨很大的難度,難以維護,代碼庫。


集合類

public class Collection 
{ 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public long Id { get; set; } 

    public virtual List<Album> Albums { get; set; } = new List<Album>(); 
} 

Album類

public class Album 
{ 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public long Id { get; set; } 

    [Required] 
    [ForeignKey("Collection")] 
    public long CollectionId { get; set; } 

    public virtual Collection Collection { get; set; } 
} 

的DbContext子類

public class DataEntities : DbContext 
{ 
    public virtual DbSet<Collection> Collections { get; set; } 
    public virtual DbSet<Album> Albums { get; set; } 

    public DataEntities() : base("name=Connection") 
    { 
     Configuration.ProxyCreationEnabled = false; 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Album>() 
      .HasRequired(a => a.Collection) 
      .WithMany(c => c.Albums) 
      .HasForeignKey(a => a.CollectionId) 
      .WillCascadeOnDelete(true); 
     modelBuilder.Entity<Collection>() 
      .HasMany(c => c.Albums) 
      .WithRequired(a => a.Collection) 
      .WillCascadeOnDelete(true); 
    } 
} 
+0

聽起來像它的使用SQLite不支持級聯刪除HTTP的問題://計算器。 com/questions/10719425/foreign-key-constraint-on-delete-cascade-not-working-in-sqlite-database-on-andro –

+0

3.14.2(System.Data.SQLite 1.0.103)。 它似乎並沒有與SQLite恕我直言,因爲我的表中的外鍵允許爲空,加上異常堆棧跟蹤最深的調用是'System.Data.Entity.Core.Objects.ObjectContext.PrepareToSaveChanges(SaveOptions選項) '這似乎還沒有與SQLite相關。 –

+0

您在映射[必需] [ForeignKey]中將其指定爲非空,這就是爲什麼EF令人困惑。 –

回答

1

在EF中應用分離的對象圖修改一直不清楚。這是失敗的原因之一。

假設Collection實體傳遞給Delete方法有Albums集合填充(至少這是我如何能夠重現異常)。該生產線

Context.Entry(entity).State = EntityState.Deleted; 

做了兩兩件事:重視entity和所有Albumentity.Albums對象的背景,標誌entityDeleted,和(注意!)的Album對象作爲Modified。當您調用SaveChanges時,這會導致錯誤行爲,並在最後生成有問題的異常。

解決此錯誤行爲有兩種方法(解決方法)。

第一個是與

Context.Collections.Attach(entity); 
Context.Collections.Remove(entity); 

的效果類似於以上所描述的,用的三個重要區別現在相關Album對象ARTE標記爲Deleted,這允許成功地執行,以取代上面的行SaveChanges

的缺點是,現在SaveChanges用於刪除Collection,這是低效的,並沒有太大的意義,因爲級聯刪除會處理,完美的數據庫內發出對每個Album一個DELETE命令的命令之前。

第二個選項是附接實體之前保持代碼原樣,但清除相關的集合:

entity.Albums = null; 
Context.Entry(entity).State = EntityState.Deleted; 

這允許成功執行SaveChanges,而且只爲實體生成單個DELETE命令。

缺點是您需要編寫額外的代碼,並且不要忘記任何支持級聯刪除的子集合,並且不需要爲需要級聯更新的集合(即具有可選關係,需要使用NULL更新FK字段)執行此操作。

選擇是你的。

+0

它的工作原理,謝謝你的詳細解釋,即使這聽起來對我來說非常不合邏輯/反直覺(EF,不是你的解釋)。如果WillCascadeOnDelete(true)沒有自然處理級聯刪除,那麼它的作用是什麼?你確定你的第一個解決方法是爲每個'Album'使用'delete'命令而不是批處理'delete',就像'DELETE FROM Albums WHERE CollectionId = xxx'? –

+0

是的,我在回答之前對它進行了測試。它執行N個命令,如'DELETE FROM Albums WHERE Id = @ 0'。最新的EF6.1.3,SqlServer數據庫。「WillCascadeOnDelete」的好處是什麼?它用於在數據庫中創建相應的約束。 –

+0

但我不認爲數據庫很重要,因爲這似乎是由EF基礎架構執行的,而數據庫提供者只是生成相應的SQL命令。 –

0

根據您的意見,您正在映射到預先存在的數據庫(EF沒有生成它)。 CascadeOnDelete隻影響數據庫的生成。如果數據庫沒有在表上配置CascadeOnDelete,那麼當EF嘗試刪除並且Sqlite不符合時,EF會感到困惑。

此外,您還將外鍵映射爲不可爲空且必需(冗餘順序),但在數據庫中外鍵可爲空。因爲你說了什麼,EF認爲它是無效的。

如果您修復了您的映射(從CollectionID屬性中刪除所需的註釋並將其類型更改爲int?而不是int,那麼您應該修復問題實際上,將DbContext類中的映射從HasRequired更改爲HasOptional ... 。?從那裏它應該工作

要麼或更改數據庫本身的表定義的sqlite的版本,你使用的是哪個

+0

我只在數據庫中允許空值用於調試目的。現在我把它放回'NOT NULL'並且FK是'ON DELETE CASCADE'。我不想將我的映射從HasRequired更改爲HasOptional,因爲在我的設計中,一張專輯總是與一個集合關聯(用戶界面顯示集合,然後顯示專輯,所以如果專輯沒有關聯到集合,它會永遠不會顯示)。 –

+0

所以,你把它放回不爲空,並刪除級聯做到這一點解決了這個問題? –

+0

不,這是最初的狀態,在我改變之前確定它與DB無關。 Ivan Stoev解決了我的兩個問題:首先提出瞭解決方案中的兩個解決方法,然後指出DbContext應始終是唯一的。 –