使用 Entity Framework Fluent API 的一对一可选关系

Posted

技术标签:

【中文标题】使用 Entity Framework Fluent API 的一对一可选关系【英文标题】:One to one optional relationship using Entity Framework Fluent API 【发布时间】:2013-08-16 21:57:34 【问题描述】:

我们希望使用 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 可能有一个LoyaltyUserDetail,但LoyaltyUserDetail 必须有一个PIIUser。 我们尝试了这些流畅的方法技术。

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

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

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

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

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

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

【问题讨论】:

【参考方案1】:

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

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

你需要的是 optional 一方面是 required

这是 Programming E.F. Code First 书中的一个示例

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

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

在两个相关的类中,您使用每个类型的主键,而不是外键。即,您不会使用 LoyaltyUserDetailIdPIIUserId 属性。相反,这种关系取决于两种类型的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 );

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

你可以从另一端开始:

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

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

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

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

【讨论】:

这不会限制用户选择哪个 FK 字段吗? (我想他想要那个,但他没有报告,所以我不知道),因为他似乎想在课堂上出现一个 FK 字段。 同意。这是 EF 工作方式的限制。 1:1 和 1:0..1 取决于主键。否则,我认为您会误入 EF 仍然不支持的“唯一 FK”。 :((你更像是一个 db 专家......对吗......这真的是关于一个独特的 FK 吗?)并且不会出现在即将到来的 Ef6 中,因为:entityframework.codeplex.com/workitem/299 是的@FransBouma,我们想使用 PIIUserId 和 LoyaltUserId 字段作为外键。但是正如你和朱莉提到的那样,EF 在这种情况下限制了我们。感谢您的回复。 @JulieLerman 你为什么使用WithOptionalWithRequiredDependentWithRequiredOptional 有什么不同? WithOptional 指向一个可选的关系,例如0..1(零或一),因为 OP 说“PIIUser 可能有 LoyaltyUserDetail”。而你导致第二个问题的困惑是因为你有一个错误的术语。 ;) 这些是 WithRequiredDependent 和 WithRequiredPrincipal。这更有意义吗?指向所需的结束,即依赖(又名“孩子”)或委托人(又名“父母”)。即使在两者相等的一对一关系中,EF 也需要将一个称为主体,将一个称为从属。 HTH!【参考方案2】:

如果你在LoyaltyUserDetailPIIUser 之间有一对多的关系,那么你的映射应该是

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

EF 应该创建您需要的所有外键,只需 don't care about WithMany !

【讨论】:

Julie Lerman 的回答被接受(并且应该继续接受,恕我直言),因为它回答了问题并详细说明了为什么它是支持细节和讨论的正确方法。复制和粘贴答案肯定可以在 SO 中使用,我自己也使用过一些,但作为专业开发人员,您应该更关心成为更好的程序员,而不仅仅是构建代码。也就是说,jr 做对了,尽管是在一年之后。 @ClickOk 我知道这是一个老问题和评论,但这不是正确的答案,因为它使用一对多作为解决方法,这不会使一对一或一对零关系【参考方案3】:

您的代码有几处问题。

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

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

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

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

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

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

【讨论】:

【参考方案4】:
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 上的问题

更新删除记录时

【讨论】:

这不会按预期工作。这会导致迁移代码不使用 LoyaltyUserId 作为外键,因此 User.Id 和 LoyaltyUser.Id 最终将具有相同的值,而 LoyaltyUserId 将留空。【参考方案5】:

尝试将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; 
    ...

【讨论】:

我们在所有这些流畅的 API 方法之前尝试了数据注释。但是数据注释不起作用。如果添加上面提到的数据注释,EF 会抛出此异常 => 无法确定类型“LoyaltyUserDetail”和“PIIUser”之间关联的主体端。此关联的主体端必须使用关系流式 API 或数据注释显式配置。 @İlkayİlknur 如果只将ForeignKey 属性添加到关系的一端会发生什么?即仅在PIIUser LoyaltyUserDetail Ef 抛出同样的异常。 @p.s.w.g 为什么在 PIIUser 使用 FK? LoyaltyUserDetail 有一个 FK,而不是 PIIUser。所以它必须是 LoyaltyUserDetail 的 PIIUserId 属性中的 [Key, ForeignKey("PIIUser")]。试试this @user808128 可以将注解放在导航属性或ID上,没有区别。【参考方案6】:

这对原始发布者没有用,但是对于仍然在 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 属性的方法,但都没有奏效)。

【讨论】:

这应该是公认的答案:)【参考方案7】:

与上述解决方案混淆的一件事是主键在两个表中都定义为“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);

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

【讨论】:

以上是关于使用 Entity Framework Fluent API 的一对一可选关系的主要内容,如果未能解决你的问题,请参考以下文章

csharp 使用Entity Framework和OracleParameters执行查询

转:Entity Framework 5.0 Code First全面学习

Entity Framework Code First

Entity Framework 5.0 Code First全面学习

为啥 Entity framework Entity Master Details Entity Edit

csharp 使用Entity Framework将数据添加到表中。