草庐IT

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

coder 2024-05-31 原文

我试图在公共(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# - Entity Framework 6.1 Code First Cascading Delete with TPH 用于派生类型上的一对一关系的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  3. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  4. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  5. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  6. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  7. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  8. ruby - inverse_of 是否适用于 has_many? - 2

    当我使用has_one时,它​​工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290

  9. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  10. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

随机推荐