EF6代码优先继承反向引用

时间:2014-01-28 22:03:50

标签: entity-framework inheritance ef-code-first table-per-type

我正在使用Entity Framework 6代码优先编写DAL。我有一个每表类型的继承结构。

我有四个类:AbstractMaster,ConcreteMaster,AbstractDetail和ConcreteDetail。直观地说,具体类继承自抽象类,并且抽象和具体的主和细节之间存在一对多的关系。每种类型的表格是一种要求。

如果我将ConcreteDetail添加到ConcreteMaster实体并保存更改(DbContext),则会收到外键错误。原因是已经设置了从ConcreteDetail到ConcreteMaster的反向引用,但是尚未设置从AbstractDetail到AbstractMaster的反向引用。

如果我在'摘要'中删除了一对多的关系。等级,然后我的测试通过。虽然数据完整性仍然在“具体”的情况下得到执行。级别,数据库仍然缺少合法的外键。看起来像一个有效的用例?

有什么建议吗?

谢谢, 约翰

1 个答案:

答案 0 :(得分: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属性的setter中设置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();
    }
}