实体框架6.1代码优先级联使用TPH删除派生类型的一对一关系

Sve*_* M. 11 c# sql entity-framework code-first

我试图在公共基类和不相关的类的派生类之间创建2个一对一关系,这样当我删除父行时,数据库中的子行将被删除.我已经沉思了这个问题好几天了,我已经尝试了所有(对我来说)可以想象的流畅api中的关系组合.到目前为止没有令人满意的结果.这是我的设置:

public class OtherType
{
 public int ID {get; set;}

 public int? DerivedTypeAID {get; set;}
 public virtual DerivedTypeA DerivedType {get; set;}

 public int? DerivedTypeBID {get; set;}
 public virtual DerivedTypeB DerivedType {get; set;}
}


public abstract class BaseType
{
  public int ID {get; set;}
  public string ClassName {get; set;}
  public virtual OtherType {get; set;}
}


public class DerivedTypeA : BaseType
{
 public string DerivedProperty {get; set;}
}

public class DerivedTypeB : BaseType
{
 public string DerivedProperty {get; set;}
}

public class MyContext : DbContext
{
 public MyContext()
        : base("name=MyContext")
    {
    }

    public DbSet<OtherType> OtherTypes { get; set; }
    public DbSet<BaseType> BaseTypes { get; set; }



    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var m = modelBuilder;

        m.Entity<OtherType>().HasOptional(_ => _.DerivedTypeA)
            .WithMany().HasForeignKey(_ => _.DerivedTypeAID).WillCascadeOnDelete(true);
        m.Entity<OtherType>().HasOptional(_ => _.DerivedTypeB)
            .WithMany().HasForeignKey(_ => _.DerivedTypeBID).WillCascadeOnDelete(true);

        m.Entity<DerivedTypeA>().HasRequired(_ => _.OtherType).WithMany().HasForeignKey(_ => _.ID).WillCascadeOnDelete(true);
        m.Entity<DerivedTypeB>().HasRequired(_ => _.OtherType).WithMany().HasForeignKey(_ => _.ID).WillCascadeOnDelete(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

这完全适用于级联删除部分.EF使用DELETE CASCADE为每个引用的DerivedType在父表OtherType上创建外键.在子表(TPH => BaseTypes)上,它使用DELETE RESTRICT创建一个外键.我希望我的代码中的最后两行可以使用DELETE CASCADE创建所需的外键.因为这是我最接近使它完全工作(并且没有保存我以前的任何尝试)我会留下它并希望我已经解释了一切,以便有人可以指出我朝着正确的方向.谢谢!

更新#1

我现在转向使用EF TPC,希望能够以这种方式解决我的问题.它没.所以这里有另外一个细节和一个ERD解释我的问题,希望有人可以帮助我,因为我达到了某种状态,你开始歇斯底里地笑着拔出你的头发.这将是我的EF模型:

多数民众赞成我如何使用Code First创建它:

public abstract class BaseType
{
    public int BaseTypeId { get; set; }
}

public class DerivedTypeA : BaseType
{
    public virtual OtherType OtherType { get; set; }
}

public class DerivedTypeB : BaseType
{
    public virtual OtherType OtherType { get; set; }
}

public class OtherType
{
    public int Id { get; set; }

    public virtual DerivedTypeA DerivedTypeA { get; set; }

    public virtual DerivedTypeB DerivedTypeB { get; set; }
}

public class TPCModel : DbContext
{

    public TPCModel()
        : base("name=TPCModel")
    {

    }

    public DbSet<BaseType> BaseTypes { get; set; }
    public DbSet<OtherType> OtherTypes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var m = modelBuilder;

        m.Entity<BaseType>().Property(_ => _.BaseTypeId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        m.Entity<DerivedTypeA>().Map(x =>
            {
                x.MapInheritedProperties();
                x.ToTable("DerivedTypeA");
            });

        m.Entity<DerivedTypeB>().Map(x =>
            {
                x.MapInheritedProperties();
                x.ToTable("DerivedTypeB");
            });

        m.Entity<DerivedTypeA>().HasRequired(_ => _.OtherType)
            .WithOptional(_ => _.DerivedTypeA).WillCascadeOnDelete(true);

        m.Entity<DerivedTypeB>().HasRequired(_ => _.OtherType)
            .WithOptional(_ => _.DerivedTypeB).WillCascadeOnDelete(true);

    }


}
Run Code Online (Sandbox Code Playgroud)

从该代码创建了此数据库架构:

两个DerivedTypes-Table都有它们的主键,也是引用BaseTypeId-Column on 的外键BaseTypes.

为了创建Code First,我按照这里的指示: - http://weblogs.asp.net/manavi/archive/2011/01/03/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5- part-3-table-per-concrete-type-tpc-and-chosen-strategy-guidelines.aspx
- http://msdn.microsoft.com/en-us/data/jj591620#RequiredToOptional

我尝试使用以下代码将记录提交到数据库:

using (var ctx = new TPCModel())
{
    Database.SetInitializer(new DropCreateDatabaseAlways<TPCModel>());
     ctx.Database.Initialize(true);

    ctx.OtherTypes.Add(new OtherType()
    {
        DerivedTypeA = new DerivedTypeA() { BaseTypeId=1 },
        DerivedTypeB = new DerivedTypeB() { BaseTypeId=2 }
    });

    ctx.SaveChanges(); // Exception throws here
}
Run Code Online (Sandbox Code Playgroud)

EF抛出此异常消息:

Additional information: The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Saving or accepting changes failed because more than one entity of type 'EF6.CodeFirst.TPC.Model.SecondConcreteType' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
Run Code Online (Sandbox Code Playgroud)

至于如何解决这个不幸的情况应该看起来像我有一个非常好的概念.当使用Table-per-Class-Strategy(TPC)时,必须自己处理主键的生成,因此当您有两个完全相同的主键,而不是数据库级别的完全不相关的表但是哪个共享时,EF不会感到困惑EF级别的公共基类.不幸的是,在我链接的第一个URL中提出的方法并没有缓解这个问题,因为我的DerivedType-Objects 上的外键在这里最终都是相同的,因为它们引用了-Table 上的主键,OtherTypes这显然是相同的该表中的独特记录.多数民众赞成的问题.

我假设的解决方案将涉及表中的两个附加列,OtherTypes每个列是表中的一个外键的目标DerivedTypes.但我绝对不知道如何在EF中实现它.无论我到目前为止尝试了什么,通常最终都会遇到一些例外,即如何只能为大多数派生类型(事实上是DerivedTypeADerivedTypeB)提供层次结构独立关联,或者其他验证异常抱怨多重性必须many在关系结束之一上.

我必须指出,我创建的模型正是我所需要的,因为我在一个更大的模型中工作,该模型使用递归编组系统和AutoMapper来映射两个层.那就是说我想请你找一个所提出的模型的解决方案,而不是提出一种不同的模型或解决方法,我偏离了zero..one to one映射.

更新#2

我厌倦了EF6的麻烦来创建从继承层次结构中的派生类到其他无关类型类的关联,所以我继续完全重写了我的数据模型以排除任何类型的层次结构(没有TPH/TPT/TPC) .我在大约5个小时内完成所有映射,使用流畅的api和播种整个表格.级联删除的工作方式与我在模型中的任何位置设置完全相同.我仍然不会放弃听到有人解决这个问题的方法,但如果不能解决这个问题,我仍会活着.

Rog*_*nez 1

我认为您的问题与 OtherType 类中导航属性的类型有关。

我认为在这种情况下你不能拥有强类型属性。

这有一个根本原因,即循环级联删除您的模型所暗示的。

作为辅助解决方法,既然您已经找到了一个,请尝试下面我在类似场景中使用的模型:(Person = OtherType、PersonDetail = BaseType、HumanBeing = DerivedTypeA、Corporation = DerivedTypeB)

public class Person
{
    public Guid Id { get; set; }

    public string Designation { get; set; }

    public virtual PersonDetail Detail { get; set; }

    public virtual Person AggregatedOn { get; set; }

    protected ICollection<Person> aggregationOf;
    public virtual ICollection<Person> AggregationOf
    {
        get { return aggregationOf ?? (aggregationOf = new HashSet<Person>()); }
        set { aggregationOf = value; }
    }
}

public abstract class PersonDetail
{
    public Guid Id { get; set; }

    public virtual Person Personne { get; set; }
}

public class Corporation : PersonDetail
{
    public string Label { get; set; }
}

public class HumanBeing : PersonDetail
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class ReferentialContext : DbContext
{
    public ReferentialContext()
        : base("ReferentialContext")
    {
    }

    public ReferentialContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    public DbSet<Person> Personnes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name.ToUpper()));
        modelBuilder.Properties().Configure(p => p.HasColumnName(p.ClrPropertyInfo.Name.ToUpper()));

        modelBuilder.Configurations.Add(new PersonConfiguration());
        modelBuilder.Configurations.Add(new PersonDetailConfiguration());

    }
}

class PersonConfiguration : EntityTypeConfiguration<Person>
{
    public PersonConfiguration()
    {
        this.HasMany(p => p.AggregationOf)
            .WithOptional(p => p.AggregatedOn);
    }
}

class PersonDetailConfiguration : EntityTypeConfiguration<PersonDetail>
{
    public PersonDetailConfiguration()
    {
        this.Property(p => p.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        this.HasRequired(p => p.Personne)
            .WithRequiredDependent(p => p.Detail);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在你的模型和我的模型之间看到的唯一区别是我不“关心”我的 Person (OtherType) 中的实际属性的类型,因为我总是可以使用 OfType linq 函数来检查我的 Person 是否属性是人类 (DerivedTypeA) 或公司 (DerivedTypeB)。