80

我们希望使用 Entity Framework Code First 来使用一对一的可选关系。我们有两个实体。

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

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

PIIUser可能有,LoyaltyUserDetailLoyaltyUserDetail必须有PIIUser。我们尝试了这些流畅的方法技术。

modelBuilder.Entity<PIIUser>()
            .HasOptional(t => t.LoyaltyUserDetail)
            .WithOptionalPrincipal(t => t.PIIUser)
            .WillCascadeOnDelete(true);

这种方法没有在表中创建LoyaltyUserDetailId外键。PIIUsers

之后我们尝试了以下代码。

modelBuilder.Entity<LoyaltyUserDetail>()
            .HasRequired(t => t.PIIUser)
            .WithRequiredDependent(t => t.LoyaltyUserDetail);

但是这次 EF 没有在这两个表中创建任何外键。

你对这个问题有什么想法吗?我们如何使用实体框架fluent api创建一对一的可选关系?

4

7 回答 7

104

EF Code First 支持1:11:0..1关系。后者是您正在寻找的(“一到零或一”)。

您对流利的尝试是说在一种情况下两端都是必需的,在另一种情况下两端都是可选的。

您需要的一方面是可选的,另一方面是必需的。

这是 Programming EF Code First 书中的一个示例

modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);

PersonPhoto实体有一个导航属性PhotoOf,称为指向一个Person类型。该Person类型有一个称为Photo指向该类型的导航属性PersonPhoto

在两个相关的类中,您使用每种类型的主键,而不是外键。即,您不会使用LoyaltyUserDetailIdorPIIUserId属性。相反,这种关系取决于Id这两种类型的字段。

如果你使用上面的 fluent API,你不需要指定LoyaltyUser.Id为外键,EF 会计算出来。

因此,无需您的代码来测试自己(我讨厌从头开始这样做)...我会将其转换为您的代码

public class PIIUser
{
    public int Id { get; set; }    
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }    
    public PIIUser PIIUser { get; set; }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<LoyaltyUserDetail>()
  .HasRequired(lu => lu.PIIUser )
  .WithOptional(pi => pi.LoyaltyUserDetail );
}

也就是说 LoyaltyUserDetailsPIIUser属性是必需的,而 PIIUser 的LoyaltyUserDetail属性是可选的。

你可以从另一端开始:

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

现在说 PIIUser 的LoyaltyUserDetail属性是可选的,而 LoyaltyUser 的PIIUser属性是必需的。

您总是必须使用模式 HAS/WITH。

HTH 和 FWIW,一对一(或一对零/一)关系是首先在代码中配置的最令人困惑的关系之一,因此您并不孤单!:)

于 2013-08-15T13:50:11.380 回答
28

就像你之间有一对多关系一样LoyaltyUserDetailPIIUser所以你的映射应该是

modelBuilder.Entity<LoyaltyUserDetail>()
       .HasRequired(m => m.PIIUser )
       .WithMany()
       .HasForeignKey(c => c.LoyaltyUserDetailId);

EF 应该创建您需要的所有外键,而不关心 WithMany

于 2014-09-24T08:25:09.700 回答
3

您的代码有几处问题。

1 :1关系是:PK<-PK,其中一个 PK 侧也是 FK,或PK<-FK+UC,其中 FK 侧是非 PK 并具有 UC。您的代码显示您有FK<-FK,因为您定义双方都有一个 FK 但这是错误的。我侦察PIIUser是PK方,LoyaltyUserDetail是FK方。这意味着PIIUser没有 FK 字段,但LoyaltyUserDetail有。

如果1:1关系是可选的,则 FK 端必须至少有 1 个可空字段。

上面的 pswg 确实回答了您的问题,但犯了一个错误,即他/她还在 PIIUser 中定义了一个 FK,正如我上面所描述的,这当然是错误的。所以在 中定义可以为空的外键字段,在LoyaltyUserDetail中定义属性LoyaltyUserDetail将其标记为外键字段,但不要在 中指定外键字段PIIUser

您会在 pswg 的帖子下方得到您在上面描述的异常,因为没有一方是 PK 方(原则结束)。

EF 不擅长 1:1,因为它无法处理独特的约束。我不是 Code first 方面的专家,所以我不知道它是否能够创建 UC。

(编辑)顺便说一句:A 1:1 B(FK)意味着只创建了 1 个 FK 约束,在 B 的目标上指向 A 的 PK,而不是 2。

于 2013-08-15T09:27:30.973 回答
3
public class User
{
    public int Id { get; set; }
    public int? LoyaltyUserId { get; set; }
    public virtual LoyaltyUser LoyaltyUser { get; set; }
}

public class LoyaltyUser
{
    public int Id { get; set; }
    public virtual User MainUser { get; set; }
}

        modelBuilder.Entity<User>()
            .HasOptional(x => x.LoyaltyUser)
            .WithOptionalDependent(c => c.MainUser)
            .WillCascadeOnDelete(false);

这将解决REFERENCEFOREIGN KEYS上的问题

更新删除记录时

于 2017-07-19T06:44:00.140 回答
2

尝试将ForeignKey属性添加到LoyaltyUserDetail属性:

public class PIIUser
{
    ...
    public int? LoyaltyUserDetailId { get; set; }
    [ForeignKey("LoyaltyUserDetailId")]
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
    ...
}

PIIUser财产:

public class LoyaltyUserDetail
{
    ...
    public int PIIUserId { get; set; }
    [ForeignKey("PIIUserId")]
    public PIIUser PIIUser { get; set; }
    ...
}
于 2013-08-14T19:21:14.840 回答
2

这对原始海报没有用,但对于仍在 EF6 上需要外键与主键不同的任何人,以下是如何做到这一点:

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

    //public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

modelBuilder.Entity<PIIUser>()
    .HasRequired(t => t.LoyaltyUserDetail)
    .WithOptional(t => t.PIIUser)
    .Map(m => m.MapKey("LoyaltyUserDetailId"));

请注意,您不能使用该LoyaltyUserDetailId字段,因为据我所知,它只能使用 fluent API 指定。(我已经尝试了三种使用该ForeignKey属性的方法,但都没有奏效)。

于 2020-06-29T10:08:40.277 回答
1

与上述解决方案混淆的一件事是主键在两个表中都定义为“ Id”,如果您有基于表名的主键它将不起作用,我已经修改了类以说明相同,即可选表不应该定义它自己的主键,而是应该使用与主表相同的键名。

public class PIIUser
{
    // For illustration purpose I have named the PK as PIIUserId instead of Id
    // public int Id { get; set; }
    public int PIIUserId { get; set; }

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    // Note: You cannot define a new Primary key separately as it would create one to many relationship
    // public int LoyaltyUserDetailId { get; set; }

    // Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
    public int PIIUserId { get; set; } 
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

然后跟着

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

可以解决问题,公认的解决方案无法清楚地解释这一点,它让我花了几个小时才找到原因

于 2018-01-17T18:49:34.053 回答