Entity Framework: 与主体外键的一对零或一对关系

Entity Framework: one to zero or one relationship with foreign key on principal

我有一个 1:0..1 关系,我想使用 fluent API 将其映射到 EF 6。该关系由一个委托人组成,委托人可能有也可能没有依赖者。受抚养人必须始终有委托人。

在主体中,我需要访问依赖者的 Id。

我的代码如下所示:

public class Principal
{
    public int Id {get; private set; }

    public int? DependentId { get; private set; }
    public virtual Dependent Dependent { get; private set; }
}

public class Dependent
{
    public int Id { get; private set; }

    public virtual Principal Principal { get; private set; }
}

我的映射如下所示:

    public class PrincipalMap : EntityTypeConfiguration<Principal>
    {
        public PrincipalMap()
        {
            ToTable("PRINCIPALS");

            HasKey(x => x.Id);

            Property(x => x.Id)
                .HasColumnName("PRINCIPALID")
                .IsRequired()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(x => x.DependentId)
                .HasColumnName("DEPENDENTID")
                .IsOptional();
        }
    }

    public class DependentMap : EntityTypeConfiguration<Dependent>
    {
        public DependentMap()
        {
            ToTable("DEPENDENTS");

            HasKey(x => x.Id);

            Property(x => x.Id)
                .HasColumnName("DEPENDENTID")
                .IsRequired()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            HasRequired(x => x.Principal).WithOptional(x => x.Dependent).Map(x => x.MapKey("PRINCIPALID")).WillCascadeOnDelete();
        }
    }

这会导致以下迁移:

            CreateTable(
                "dbo.PRINCIPALS",
                c => new
                    {
                        PRINCIPALID = c.Int(nullable: false, identity: true),
                        DEPENDENTID = c.Int(),
                    })
                .PrimaryKey(t => t.PRINCIPALID);

            CreateTable(
                "dbo.DEPENDENTS",
                c => new
                    {
                        DEPENDENTID = c.Int(nullable: false, identity: true),
                        PRINCIPALID = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.DEPENDENTID)
                .ForeignKey("dbo.PRINCIPALS", t => t.PRINCIPALID, cascadeDelete: true)
                .Index(t => t.PRINCIPALID);

如您所见,列 DEPENDENTID 不是外键。当运行程序将依赖对象关联到主体时,DependentId属性保持为空,即EF不识别它是相关的给依赖者本身。

我做错了什么?

是的,这很棘手并且是一个 EF 错误 IMO。我使用的解决方法是伪 1:M:

HasRequired(x => x.Principal)
  .WithMany()
  .HasForeignKey(x => x.DependentId);
  .WillCascadeOnDelete();

http://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations

在 DependentMap 中,您将字段 DEPENDENTID 声明为 DEPENDENT table 的主键,数据库生成(标识)因此它永远不会是外键。您不能随意更改它(使其指向您选择的实体)。

此外,对于 EF(和 E/R),您不需要两列(每个 table 一个)来建立 1-0..1 关系。您只能有一列(不可为空)。

在您的情况下,此模型应该有效:

public class Principal
{
    public int Id { get; private set; }

    public virtual Dependent Dependent { get; private set; }
}

public class Dependent
{
    public int Id { get; private set; }

    public virtual Principal Principal { get; private set; }
}

public class PrincipalMap : EntityTypeConfiguration<Principal>
{
    public PrincipalMap()
    {
        ToTable("PRINCIPALS");

        HasKey(x => x.Id);

        Property(x => x.Id)
            .HasColumnName("PRINCIPALID")
            .IsRequired()
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}

public class DependentMap : EntityTypeConfiguration<Dependent>
{
    public DependentMap()
    {
        ToTable("DEPENDENTS");

        HasKey(x => x.Id);

        Property(x => x.Id)
            .HasColumnName("DEPENDENTID")
            .IsRequired()
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        HasRequired(x => x.Principal).WithOptional(x => x.Dependent).Map(x => x.MapKey("PRINCIPALID")).WillCascadeOnDelete();
    }
}

在这种情况下,table 创建语句(由 EF 提供程序生成)应该类似于

ExecuteNonQuery==========
CREATE TABLE [DEPENDENTS] (
 [DEPENDENTID] int not null identity(1,1)
, [PRINCIPALID] int not null
);
ALTER TABLE [DEPENDENTS] ADD CONSTRAINT [PK_DEPENDENTS_204c4d57] PRIMARY KEY ([DEPENDENTID])
ExecuteNonQuery==========
CREATE TABLE [PRINCIPALS] (
 [PRINCIPALID] int not null identity(1,1)
);
ALTER TABLE [PRINCIPALS] ADD CONSTRAINT [PK_PRINCIPALS_204c4d57] PRIMARY KEY ([PRINCIPALID])
ExecuteNonQuery==========
CREATE INDEX [IX_PRINCIPALID] ON [DEPENDENTS] ([PRINCIPALID])
ExecuteNonQuery==========
ALTER TABLE [DEPENDENTS] ADD CONSTRAINT [FK_DEPENDENTS_PRINCIPALS_PRINCIPALID] FOREIGN KEY ([PRINCIPALID]) REFERENCES [PRINCIPALS] ([PRINCIPALID])

(我省略了级联删除,但也应该清楚)。

E/R 模型是标准形式(并且是唯一适用于 EF 的模型)。
顺便说一句,如果您访问 Principal.Dependent 属性 EF 将生成一个类似于 selected * from dependent where PRINCIPALID = <principal_id> 的查询,其中主体实体的 ID 在哪里,所以它确实有效。

现在,关于您的要求,要从 Principal 访问 Dependent.Id,唯一的方法是 dependentId = Principal.Dependent.Id(或者更好,dependentId = Principal.Dependent == null ? null : Principal.Dependent.Id)。

如果您真的想要 PRINCIPAL 上引用 DEPENDENT table 的外键字段怎么办?
此模型不是正常形式,因此 EF 不会处理它(对于 DBMS,您也需要编写触发器来处理它)。
我的意思是,在 R-DBMS 中,没有约束可以指定如果列 DEPENDENT.PRINCIPALID 引用 PRINCIPAL,那么列 PRINCIPAL.DEPENDENTID 应该引用原始 DEPENDENT。
在这种情况下,您需要做的是自己处理 PRINCIPAL.DEPENDENTID(即 Principal 实体必须有一个 DEPENDENTID 属性,您必须自己处理并且在导航期间不会被 EF 使用)。