2013-06-04 284 views
0

下面是一個使用EF5代碼優先方法在其最簡單的形式的情況:與實體框架刪除主體時處理相關實體5

public abstract class EntityBase<PK> 
{ 
    public PK ID { get; set; } 
} 
public class Country : EntityBase<string> 
{ 
    public string Name { get; set; } 
} 
public class Address : EntityBase<int> 
{ 
    [Required] 
    public string CountryID { get; set; } 
    public Country Country { get; set; } 
    // ... other address properties ... 
} 

AddressCountry之間的一個一對多的關係設置了無級聯刪除像這樣:

modelBuilder.Entity<Address>() 
    .HasRequired(a => a.Country) 
    .WithMany() 
    .HasForeignKey(a => a.CountryID) 
    .WillCascadeOnDelete(false); 

最後,我有調用SaveChanges底層的DbContext上原子提交數據更改的CRUD方法的通用基礎repository類。例如: -

public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK> 
{ 
    // 
    // ... other methods ... 
    // 
    public virtual void Delete(T instance) 
    { 
     // ... trigger validations, write to log, etc... 
     _dbContext.Set<T>().Remove(instance); 
     try 
     { 
      _dbContext.SaveChanges(); 
     } 
     catch(Exception ex) 
     { 
      // ... handle the error ... 
     } 
    } 
} 

第1部分:

場景:

var countryRepo = new EFRepository<Country>(); 
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" }); 

var addressRepo = new EFRepository<Address>(); 
var address = addressRepo.Save(new Address() { Country=country }); 

countryRepo.Delete(country); 

將失敗,由於依賴Address存在。然而,之後地址結尾爲空,因爲CountryID無效,因爲Address.CountryID是必需的,因此後續的SaveChanges調用會引發驗證異常,除非地址已分離。

我預計,當一個對象被刪除,EF5將是足夠聰明的,先檢查是否像上面的,並沒有找到任何任何級聯刪除約束,然後進行刪除數據。但恰恰相反,情況似乎如此。

這是正常行爲還是我做錯了什麼?

第2部分:

繼失敗SaveChanges電話,有些Addresses現在在我的DbContext無效狀態,需要恢復到原來的值。當然,我可以一直做下去明確通過創建專門的倉庫類和壓倒一切的Delete每個實體類型(CountryStateOrder等),但它的氣味大的時間。我寧願寫一些通用代碼,以便在撥打失敗的SaveChanges電話後正常恢復相關實體。

這將需要詢問DbContext以獲取實體(例如Country)爲主體的所有關係,而不管其類別是否定義到相關實體的導航屬性。

E.g. Country沒有Addresses屬性,所以我需要以某種方式在DbContext中找到CountryAddress之間的一對多關係的定義,並使用它將所有相關的Addresses恢復爲其原始值。

這可能嗎?

回答

0

回答我的問題在第2部分

這裏是我的方法上的主要終點刪除實體時檢查相關家屬多到一的關係並在家屬不暴露爲校長中的導航集合(例如,類Address具有Country屬性,但類Country沒有Addresses集合)。

的DbContext

添加以下方法上下文類:

/// <summary> 
/// Returns an array of entities tracked by the 
/// context that satisfy the filter criteria. 
/// </summary> 
public DbEntityEntry[] GetTrackedEntities<T>(
    Expression<Func<DbEntityEntry<T>, bool>> filterCriteria) 
    where T : class 
{ 
    var result = new List<DbEntityEntry>(); 
    var doesItMatch = filterCriteria.Compile(); 

    foreach (var entry in this.ChangeTracker.Entries<T>()) 
    { 
     if (doesItMatch(entry)) 
      result.Add(entry); 
    } 
    return result.ToArray(); 
} 

創建有一些依賴每個類的儲存庫,覆蓋Delete方法使用新的GetTrackedEntities<T>方法獲取所有相關的受撫養人並且:

  • 明確刪除他們,如果他們是級聯刪除的代碼
  • 從上下文脫離他們,如果他們是級聯刪除的在數據庫本身
  • 拋出一個異常,如果他們不級聯刪除的。後一種情況的

例如:一個級聯刪除將被DB自己完成的情況

public class EFCountryRepository : 
    EFReadWriteRepository<Country, string>, 
    ICountryRepository 
{ 
    public override void Delete(Country instance) 
    { 
     // Allow the Country to be deleted only if there are no dependent entities 
     // currently in the context that are NOT cascade-deletable. 
     if (
      // are there any Regions in the context that belong to this Country? 
      _dbContext.GetTrackedEntities<Region>(e => 
       e.Entity.CountryID == instance.ID || 
       e.Entity.Country == instance).Length > 0 
      || 
      // are there any Addresses in the context that belong to this Country? 
      _dbContext.GetTrackedEntities<Address>(e => 
       e.Entity.CountryID == instance.ID || 
       e.Entity.Country == instance).Length > 0 
     ) 
      throw new Exception(String.Format(
       "Country '{0}' is in use and cannot be deleted.", instance.ID)); 

     base.Delete(instance); 
    } 
    // ... other methods ... 
} 

例子,所以我們需要做的是從上下文分離的家屬:

public class EFOrderRepository : 
    EFReadWriteRepository<Order, string>, 
    IOrderRepository 
{ 
    public override void Delete(Order instance) 
    { 
     foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e => 
       e.Entity.OrderID == instance.ID || 
       e.Entity.Order == instance)) 
     { 
      _dbContext.Entry(orderItem).State = System.Data.EntityState.Detached; 
     } 
     base.Delete(instance); 
    } 
    // ... other methods ... 
} 

希望有人會發現此解決方案有幫助。