1

我正在使用實體框架6代碼優先編寫DAL。我有一個table-per-type繼承結構。EF6代碼優先繼承back-reference

我有四個類:AbstractMaster,ConcreteMaster,AbstractDetail和ConcreteDetail。直觀上,具體類繼承了抽象類,並且抽象和具體的主和細節之間存在一對多的關係。每種類型的表格是一項要求。

如果我將ConcreteDetail添加到ConcreteMaster實體並保存更改(DbContext),則會收到外鍵錯誤。原因是已經設置了從ConcreteDetail到ConcreteMaster的反向引用,但是從AbstractDetail到AbstractMaster的反向引用尚未設置。

如果我在「抽象」級別刪除一對多關係,則測試通過。雖然數據完整性仍在「具體」級別執行,但數據庫仍缺少合法的外鍵。看起來像一個有效的用例?

有什麼建議嗎?

感謝, 約翰

回答

0

使用ObservableCollection<TEntity>的大師班集合導航屬性。在ConcreteMaster類中爲每個集合的CollectionChanged事件實施處理程序方法。這裏的想法是,當一個項目被添加或刪除的一個集合,您將添加/從其他集合中移除相同的項目:

AbstractMaster類:

[Table("AbstractMaster")] 
public abstract class AbstractMaster 
{ 
    public int Id { get; set; } 

    public virtual ObservableCollection<AbstractDetail> AbstractDetails { get; private set; } 

    public AbstractMaster() 
    { 
     AbstractDetails = new ObservableCollection<AbstractDetail>(); 
    } 
} 

ConcreteMaster類:

[Table("ConcreteMaster")] 
public class ConcreteMaster : AbstractMaster 
{ 
    public virtual ObservableCollection<ConcreteDetail> ConcreteDetails { get; private set; } 

    public ConcreteMaster() 
    { 
     ConcreteDetails = new ObservableCollection<ConcreteDetail>(); 

     ConcreteDetails.CollectionChanged += ConcreteDetails_CollectionChanged; 

     base.AbstractDetails.CollectionChanged += AbstractDetails_CollectionChanged; 
    } 

    void AbstractDetails_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     var newDetails = new List<ConcreteDetail>(); 
     var oldDetails = new List<ConcreteDetail>(); 

     bool nonConcreteDetailAdded = false; 

     switch(e.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       var newCollection = sender as ReadOnlyObservableCollection<AbstractDetail>; 
       nonConcreteDetailAdded = !newCollection.All(ad => ad is ConcreteDetail); 

       if(!nonConcreteDetailAdded) 
       { 
        newDetails.AddRange(e.NewItems.Cast<ConcreteDetail>()); 
       } 
       break; 
      default: 
       if(null != e.OldItems) 
       { 
        oldDetails.AddRange(e.OldItems.Cast<ConcreteDetail>()); 
       } 

       if(null != e.NewItems) 
       { 
        nonConcreteDetailAdded = !e.NewItems.Cast<AbstractDetail>().All(ad => ad is ConcreteDetail); 

        if(!nonConcreteDetailAdded) 
        { 
         newDetails.AddRange(e.NewItems.Cast<ConcreteDetail>()); 
        } 
       } 
       break; 
     } 

     if(nonConcreteDetailAdded) 
     { 
      throw new InvalidOperationException("An object of a type not derived from ConcreteDetail was added to the AbstractDetails property of a ConcreteMaster object's base class"); 
     } 

     foreach(var removed in oldDetails) 
     { 
      if(ConcreteDetails.Contains(removed)) 
      { 
       ConcreteDetails.Remove(removed); 
      } 
     } 

     foreach(var added in newDetails) 
     { 
      if(!ConcreteDetails.Contains(added)) 
      { 
       ConcreteDetails.Add(added); 
      } 
     } 
    } 

    void ConcreteDetails_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     var newDetails = new List<AbstractDetail>(); 
     var oldDetails = new List<AbstractDetail>(); 

     switch(e.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       var newCollection = sender as ReadOnlyObservableCollection<AbstractDetail>; 
       base.AbstractDetails.Clear(); 
       newDetails.AddRange(newCollection); 
       break; 
      default: 
       if(null != e.OldItems) 
       { 
        oldDetails.AddRange(e.OldItems.Cast<AbstractDetail>()); 
       } 

       if(null != e.NewItems) 
       { 
        newDetails.AddRange(e.NewItems.Cast<AbstractDetail>()); 
       } 
       break; 
     } 

     foreach(var removed in oldDetails) 
     { 
      if(base.AbstractDetails.Contains(removed)) 
      { 
       base.AbstractDetails.Remove(removed); 
      } 
     } 

     foreach(var added in newDetails) 
     { 
      if(!base.AbstractDetails.Contains(added)) 
      { 
       base.AbstractDetails.Add(added); 
      } 
     } 
    } 
} 

在細節方面,實現INotifyPropertyChanged接口AbstractDetail,引發事件時AbstractMaster屬性更改:

[Table("AbstractDetail")] 
public abstract class AbstractDetail : INotifyPropertyChanged 
{ 
    public int Id { get; set; } 

    private AbstractMaster _abstractMaster = null; 
    public AbstractMaster AbstractMaster 
    { 
     get 
     { 
      return _abstractMaster; 
     } 
     set 
     { 
      if(value != _abstractMaster) 
      { 
       _abstractMaster = value; 

       if(null != PropertyChanged) 
       { 
        PropertyChanged(this, new PropertyChangedEventArgs("AbstractMaster")); 
       } 
      } 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

最後,在ConcreteDetail類,設置在ConcreteMaster屬性的setter的base.AbstractMaster財產,並添加一個事件處理base.AbstractMaster將更新this.ConcreteMasterbase.AbstractMaster變化:

[Table("ConcreteDetail")] 
public class ConcreteDetail : AbstractDetail 
{ 
    private ConcreteMaster _concreteMaster = null; 
    public ConcreteMaster ConcreteMaster 
    { 
     get 
     { 
      return _concreteMaster; 
     } 
     set 
     { 
      if(value != _concreteMaster) 
      { 
       _concreteMaster = value; 
       base.AbstractMaster = _concreteMaster; 
      } 
     } 
    } 

    public ConcreteDetail() 
    { 
     base.PropertyChanged += ConcreteDetail_PropertyChanged; 
    } 

    void ConcreteDetail_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if(e.PropertyName == "AbstractMaster") 
     { 
      var master = base.AbstractMaster; 

      if(null == master) 
      { 
       _concreteMaster = null; 
      } 
      else if(master is ConcreteMaster) 
      { 
       _concreteMaster = master as ConcreteMaster; 
      } 
      else 
      { 
       throw new InvalidOperationException("AbstractMaster property of a ConcreteDetail object's base class was set to an instance of a class that does not derive from ConcreteDetail"); 
      } 
     } 
    } 
} 

我已經用下面的測試,這代碼:

class Program 
{ 
    static void Main(string[] args) 
    { 
     using(var db = new TestEntities()) 
     { 
      var master = new ConcreteMaster(); 

      var details = new[]{ 
       new ConcreteDetail() { Id = 1 }, 
       new ConcreteDetail() { Id = 2 }, 
       new ConcreteDetail() { Id = 3 }, 
       new ConcreteDetail() { Id = 4 } 
      }; 

      master.AbstractDetails.Add(details[ 0 ]); 
      master.ConcreteDetails.Add(details[ 1 ]); 

      details[ 2 ].AbstractMaster = master; 
      details[ 3 ].ConcreteMaster = master; 

      db.ConcreteMasters.Add(master); 
      db.AbstractDetails.Add(details[ 2 ]); 
      db.ConcreteDetails.Add(details[ 3 ]); 

      db.SaveChanges(); 
     } 

     using(var db = new TestEntities()) 
     { 
      var concreteMaster = db.ConcreteMasters.Single(); 
      var abstractMaster = db.AbstractMasters.Single(); 

      Action<string, IEnumerable<AbstractDetail>> outputDelegate = (string header, IEnumerable<AbstractDetail> details) => 
       { 
        if(details.Count() > 0) 
        { 
         Console.WriteLine("{0}: {1}", header, string.Join(", ", details.Select(ad => ad.Id.ToString()))); 
        } 
        else 
        { 
         Console.WriteLine("{0}: <empty>", header); 
        } 
       }; 

      // 1, 2, 3, 4 
      outputDelegate("AbstractMaster.AbstractDetails", abstractMaster.AbstractDetails); 
      outputDelegate("ConcreteMaster.ConcreteDetails", concreteMaster.ConcreteDetails); 

      // remove Id == 4 by way of removing from abstract collection 
      abstractMaster.AbstractDetails.Remove(abstractMaster.AbstractDetails.Single(ad => ad.Id == 4)); 
      db.SaveChanges(); 

      // 1, 2, 3 
      outputDelegate("AbstractMaster.AbstractDetails", abstractMaster.AbstractDetails); 
      outputDelegate("ConcreteMaster.ConcreteDetails", concreteMaster.ConcreteDetails); 

      // remove Id == 3 by way of removing from concrete collection 
      concreteMaster.ConcreteDetails.Remove(concreteMaster.ConcreteDetails.Single(cd => cd.Id == 3)); 
      db.SaveChanges(); 

      // 1, 2 
      outputDelegate("AbstractMaster.AbstractDetails", abstractMaster.AbstractDetails); 
      outputDelegate("ConcreteMaster.ConcreteDetails", concreteMaster.ConcreteDetails); 

      // remove Id == 2 by way of removing AbstractDetail from DbSet<AbstractDetail> 
      db.AbstractDetails.Remove(abstractMaster.AbstractDetails.Single(ad => ad.Id == 2)); 
      db.SaveChanges(); 

      // 1 
      outputDelegate("AbstractMaster.AbstractDetails", abstractMaster.AbstractDetails); 
      outputDelegate("ConcreteMaster.ConcreteDetails", concreteMaster.ConcreteDetails); 

      // remove Id == 1 by wa of removing ConcreteDetail from DbSet<ConcreteDetail> 
      db.ConcreteDetails.Remove(concreteMaster.ConcreteDetails.Single(cd => cd.Id == 1)); 
      db.SaveChanges(); 

      // <empty> 
      outputDelegate("AbstractMaster.AbstractDetails", abstractMaster.AbstractDetails); 
      outputDelegate("ConcreteMaster.ConcreteDetails", concreteMaster.ConcreteDetails); 
     } 

     var input = Console.ReadLine(); 
    } 
}