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(); 
    } 
} 
相关问题