c# - Entity Framework 6.1 Code First Cascading Delete with TPH 用于派生类型上的一对一关系

标签 c# sql entity-framework code-first

我试图在公共(public)基类和不相关类的派生类之间创建 2 个一对一的关系,这样当我删除父行时,数据库中的子行也会被删除。
几天来我一直在思考这个问题,并且我已经尝试了 fluent 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);
    }
}

除了级联删除部分之外,这完全有效。 EF 使用 DELETE CASCADE 在父表 OtherType 上为每个引用的 DerivedType 创建外键。在子表 (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);

    }


}

从该代码创建了这个数据库模式:



两个 DerivedTypes -Tables 都有它们的主键,它也是一个引用 BaseTypeId -Column 上的外键 BaseTypes

为了创建代码优先,我遵循了这里的指示:
- 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-choosing-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
}

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.

现在,对于这种不幸情况的解决方案应该如何,我有一个很好的想法。
当使用 Table-per-Class-Strategy (TPC) 时,必须自己处理主键的生成,因此当您有两个完全相同的主键用于数据库级别完全不相关但共享的表时,EF 不会感到困惑EF 级别的公共(public)基类。不幸的是,我链接的第一个 URL 中的建议方式并没有缓解这个问题,因为我的 DerivedType -Objects 上的外键在这里无论如何都将是相同的,因为它们引用了 OtherTypes -Table 上的主键,这显然是该表中的不同记录相同。
这就是问题所在。

我假设的解决方案将涉及 OtherTypes 表中的两个附加列,每一列都是 DerivedTypes 表上的一个外键的目标。但我绝对不知道如何在 EF 中实现它。到目前为止,无论我尝试过什么,通常最终都会出现一些异常,即您如何只能为最派生的类型(实际上是 DerivedTypeADerivedTypeB )或其他验证异常提示多重性必须是 many关系结束。

我必须指出,我创建的模型正是我所需要的,因为我在一个更大的模型中工作,该模型使用递归编码系统和 AutoMapper 在两层之间进行映射。也就是说,我想请您为建议的模型找到一个解决方案,而不是提出不同类型的模型或解决方法,因为我偏离了 zero..one to one 映射。

更新 #2

我已经厌倦了 EF6 在继承层次结构中创建从派生类到其他不相关类型类的关联的麻烦,所以我继续并完全重写了我的数据模型以排除任何类型的层次结构(没有 TPH/TPT/TPC) .我在大约 5 个小时内完成了使用 fluent api 的所有映射和整个表格跨度的播种。级联删除工作 正好是 ,因为我从模型中的任何地方设置它们。我仍然不会放弃听取某人对这个问题的解决方案,但如果这不会得到解决,我会继续活下去。

最佳答案

我认为您的问题与 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);
    }
}

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

关于c# - Entity Framework 6.1 Code First Cascading Delete with TPH 用于派生类型上的一对一关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23205731/

相关文章:

c# - ASP MVC 和 postgres

c# - 使用值注入(inject)器映射不同的属性值

c# - 尝试返回 IQueryable<MyType> 时出现转换错误

.net - Entity Framework 4 的数据库迁移

sql - 如何将后端数据(变量)显示到前端页面

c# - 在 SqlException 上自动重试请求

c# - 从文件上传中获取完整路径

c# - Crystal 报表 : How can I avoid nested subreports in this case?

sql - 如何在数组大小大于1的postgres中获取数组

sql - 是否有一种特殊形式的 UPDATE 语句除了检查有效 SQL 外什么都不做?