1

Я пишу DAL, используя Entity Framework 6 с кодовым кодом. У меня есть структура наследования типа «за каждый тип».EF6 code-first inheritance back-reference

У меня есть четыре класса: AbstractMaster, ConcreteMaster, AbstractDetail и ConcreteDetail. Интуитивно, конкретные классы наследуют от абстрактных, и существует взаимосвязь «один-ко-многим» между мастером и деталями для абстрактного и конкретного. Шаблон таблицы за тип является обязательным требованием.

Если я добавляю ConcreteDetail к сущности ConcreteMaster и сохраняю изменения (DbContext), я получаю ошибку внешнего ключа. Причина в том, что была установлена ​​обратная ссылка с ConcreteDetail на ConcreteMaster, но обратная ссылка от AbstractDetail на AbstractMaster не была установлена.

Если я удалю отношения «один ко многим» на «абстрактном» уровне, тогда мой тест пройдет. Хотя целостность данных по-прежнему применяется на «конкретном» уровне, в базе данных по-прежнему отсутствует законный внешний ключ. Похоже на действительный прецедент?

Любые предложения?

Спасибо, Джон

ответ

0

Использование ObservableCollection<TEntity> для сбора навигационных свойств мастер-классов. Внедрить методы обработчика для события CollectionChanged каждого собрания в классе ConcreteMaster. Идея заключается в том, когда элемент добавляется или удаляется в одной коллекции, вы будете добавлять/удалять один и тот же элемент из другой коллекции:

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 классе, установите base.AbstractMaster недвижимость в инкубаторе в ConcreteMaster имущества и добавить обработчик событий base.AbstractMaster, который будет обновлять this.ConcreteMaster при base.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(); 
    } 
} 
Смежные вопросы