【问题标题】:Entity Framework 6.1 Code First Cascading Delete with TPH for one-to-one relationship on a derived typeEntity Framework 6.1 Code First Cascading Delete with TPH 用于派生类型上的一对一关系
【发布时间】:2014-06-06 00:13:56
【问题描述】:

我正在尝试在公共基类的派生类和不相关类之间创建 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);
    }
}

除了级联删除部分之外,这完全有效。 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 都有它们的主键,它也是引用 BaseTypes 上的 BaseTypeId-Column 的外键。

为了创建 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-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 级别的通用基类。不幸的是,我链接的第一个 URL 中的建议方法并不能缓解这个问题,因为我的 DerivedType-Objects 上的外键无论如何都会在这里相同,因为它们引用了 OtherTypes-Table 上的主键对于该表中的不同记录,显然将是相同的。 这就是问题所在。

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

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

更新 #2

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

【问题讨论】:

  • +1:我现在也有同样的问题。希望有一个解决方案。

标签: c# sql entity-framework code-first


【解决方案1】:

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

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

这在您的模型所暗示的循环级联删除中具有根本原因。

作为第二种解决方法,既然您已经找到了,请尝试以下我在类似场景中使用的模型:(with 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 函数来检查是否我属性中的人员是 Humans (DerivedTypeA) 或 Corporations (DerivedTypeB)。

【讨论】:

    【解决方案2】:

    最后一行

    使用“HasDatabaseGeneratedOption”流畅的 API 或 'DatabaseGeneratedAttribute' 用于 Code First 配置。

    所以将 [key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 添加到 ids

    并将 [ForeignKey("column name")] 添加到导航属性

    要获得更好的说明,请在线查找并阅读这些书籍:

    • 编程实体框架:DbContext,ISBN:978-1-4493-1296-1
    • Microsoft ADO.NET Entity Framework Step by Step,ISBN: 978-0-73566-416-6

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多