2012-05-31 120 views
13

對於使用代碼第一次EF 5測試版的應用程序,我有:EF可以自動刪除父母未被刪除的孤兒數據嗎?

public class ParentObject 
{ 
    public int Id {get; set;} 
    public virtual List<ChildObject> ChildObjects {get; set;} 
    //Other members 
} 

public class ChildObject 
{ 
    public int Id {get; set;} 
    public int ParentObjectId {get; set;} 
    //Other members 
} 

相關的CRUD操作都是由庫,必要時進行。

OnModelCreating(DbModelBuilder modelBuilder) 

我已經建立起來:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithOptional() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 

因此,如果一個ParentObject被刪除,它ChildObjects也有同感。

但是,如果我運行:

parentObject.ChildObjects.Clear(); 
_parentObjectRepository.SaveChanges(); //this repository uses the context 

我得到異常:

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

這是有道理的,因爲實體的定義包括被破壞的外鍵約束。

我可以配置實體「清除自己」孤立或必須手動從上下文中刪除這些ChildObject(在這種情況下使用ChildObjectRepository)。

+0

幸運的是,EF隊[知道這個(http://blog.oneunicorn.com/2012/06/02/deleting-orphans-with-entity-framework/),並很可能會拿出一個內置的解決方案,不需要修改內部結構 – PinnyM

回答

25

它實際上支持,但只有當您使用Identifying relation。它也適用於代碼。你只需要定義複雜的按鍵爲您ChildObject含有IdParentObjectId

modelBuilder.Entity<ChildObject>() 
      .HasKey(c => new {c.Id, c.ParentObjectId}); 

由於定義這樣的鍵將刪除默認的約定自動遞增的ID,必須手動重新定義它:

modelBuilder.Entity<ChildObject>() 
      .Property(c => c.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

現在打電話parentObject.ChildObjects.Clear()刪除依賴對象。

Btw。你的關係映射應該使用WithRequired跟隨自己真正的類,因爲如果FK不是空的,它是不可選:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 
+0

很棒的回答。看起來我有很多需要了解EF的知識。 – StuperUser

+1

@Ladislav Mmka單向事件呢?當ChildObject不知道父對象時? – Davita

+0

@Ladislav我正在使用EF6,並試圖按照您在示例中所做的那樣進行操作。但是調用Collection.Clear()和AddOrUpdate(父類)似乎不起作用。有什麼我需要做的上下文? – Jamez

-1

這不是EF現在自動支持的東西。您可以通過覆蓋上下文中的SaveChanges並手動刪除不再具有父級的子對象來完成此操作。代碼將是這樣的:

public override int SaveChanges() 
{ 
    foreach (var bar in Bars.Local.ToList()) 
    { 
     if (bar.Foo == null) 
     { 
      Bars.Remove(bar); 
     } 
    } 

    return base.SaveChanges(); 
} 
+3

除了這個應該會給你帶來可怕的性能的好主意 –

+3

這個代碼不會爲每個SaveChanges()執行嗎? – Elisabeth

4

爲了不設置複雜的關鍵解決這個問題,你可以重寫你的DbContextSaveChanges,但隨後使用ChangeTracker來避免訪問數據庫以查找孤立對象。

首先導航屬性添加到ChildObject(你可以保持int ParentObjectId財產,如果你願意,它仍然可以正常工作):

public class ParentObject 
{ 
    public int Id { get; set; } 
    public virtual List<ChildObject> ChildObjects { get; set; } 
} 

public class ChildObject 
{ 
    public int Id { get; set; } 
    public virtual ParentObject ParentObject { get; set; } 
} 

然後使用ChangeTracker尋找孤立對象:

public class MyContext : DbContext 
{ 
    //... 
    public override int SaveChanges() 
    { 
     HandleOrphans(); 
     return base.SaveChanges(); 
    } 

    private void HandleOrphans() 
    { 
     var orphanedEntities = 
      ChangeTracker.Entries() 
      .Where(x => x.Entity.GetType().BaseType == typeof(ChildObject)) 
      .Select(x => ((ChildObject)x.Entity)) 
      .Where(x => x.ParentObject == null) 
      .ToList(); 

     Set<ChildObject>().RemoveRange(orphanedEntities); 
    } 
} 

您的配置變爲:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired(c => c.ParentObject) 
      .WillCascadeOnDelete(); 

我做了一個迭代10.000次的簡單速度測試。啓用HandleOrphans()後,花費1:01.443分鐘完成,禁用它爲0:59.326分鐘(均爲三次運行的平均值)。測試代碼如下。

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Add(new ChildObject()); 
    context.SaveChanges(); 
} 

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Clear(); 
    context.SaveChanges(); 
}