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


79

我们想使用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);

这种方法没有LoyaltyUserDetailIdPIIUsers表中创建外键。

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

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

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

您对此问题有任何想法吗?我们如何使用实体框架流利的API创建一对一的可选关系?

Answers:


101

EF Code First支持1:11:0..1建立关系。后者是您要寻找的(“一对零或一对”)。

您说的流利尝试是在一种情况下要求两端,而在另一种情况下要求两端均可选

您需要的是一端是可选的,而另一端是必需的。

这是《编程EF代码第一书》中的示例

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

PersonPhoto实体呼叫导航属性PhotoOf,它指向一个Person类型。该Person类型有一个叫做导航属性Photo指向PersonPhoto型。

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

如果您使用上述流利的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,一对一(或一对一/零/一)关系是最容易在代码中配置的关系之一,因此您并不孤单!:)


1
这是否不限制用户选择哪个FK字段?(我想他想这样,但他没有报告,所以我不知道),因为他似乎希望在班上有一个FK场。
弗朗斯·布玛

1
同意 这是EF工作方式的局限性。1:1和1:0..1取决于主键。否则,我认为您正在流向EF仍不支持的“唯一FK”。:((您更多是数据库专家...是正确的...这真的是关于唯一的FK吗?)而且不会出现在即将发布的Ef6中,如下所示:entityframework.codeplex.com/workitem/299
朱莉·莱曼(Julie Lerman)2013年

1
是@FransBouma,我们希望使用PIIUserId和LoyaltUserId字段作为外键。但是,正如您和朱莉(Julie)所述,EF限制了我们的这种情况。感谢您的答复。
伊尔卡伊İlknur

@JulieLerman为什么使用WithOptionalWithRequiredDependent和和有WithRequiredOptional什么不同?
2014年

1
WithOptional指向可选关系,例如0..1(零或一),因为OP表示“ PIIUser可能具有LoyaltyUserDetail”。而导致第二个问题的困惑是因为您错了其中一个术语。;)这些是WithRequiredDependent和WithRequiredPrincipal。这更有意义吗?指向必需端,该端要么是依赖项(也称为“子项”),要么是主要主体(即“父项”)。即使在两者相等的一对一关系中,EF也需要将一个称为主体,将一个称为从属。HTH!
朱莉·勒曼

27

就像您之间存在一对多关系一样LoyaltyUserDetailPIIUser因此映射应为

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

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


3
朱莉·勒曼(Julie Lerman)的答案被接受了(恕我直言,恕我直言),因为它回答了问题,并详细介绍了为什么这是正确的方法,并提供了详细的讨论。复制和粘贴答案肯定可以在SO上找到,我自己也用过一些,但是作为一个专业的开发人员,您应该更加关注成为一个更好的程序员,而不仅仅是构建代码。话虽如此,尽管一年后,JR还是做到了。
Mike Devenney

1
@ClickOk我知道这是一个古老的问题和评论,但这不是正确的答案,因为它使用了一对多的解决方法,但这并没有形成一对一或一对零的关系
Mahmoud Darwish

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,这当然是错误的,如上所述。因此,请在中定义可为空的FK字段,在LoyaltyUserDetail中定义属性LoyaltyUserDetail以将其标记为FK字段,但不要在中指定FK字段PIIUser

您会在pswg的帖子下方获得上面描述的异常,因为PK端(原理端)没有一面。

EF在1:1时不是很好,因为它无法处理唯一的约束。我首先不是Code方面的专家,所以我不知道它是否能够创建UC。

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


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);

这将解决参考和外上的问题

UPDATINGDELETING记录


这将无法正常工作。这将导致迁移代码不使用LoyaltyUserId作为外键,因此User.Id和LoyaltyUser.Id将以相同的值结束,而LoyaltyUserId将保留为空。
亚伦·昆南

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; }
    ...
}

1
在所有这些流利的API方法之前,我们都尝试过数据注释。但是数据注释不起作用。如果添加上面提到的数据注释,则EF抛出此异常=>无法确定类型“ LoyaltyUserDetail”和“ PIIUser”之间的关联的主要终点。必须使用关系流利的API或数据注释显式配置此关联的主要端。
伊尔卡伊İlknur

@İlkayİlknur如果ForeignKey仅将属性添加到关系的一端会发生什么?即仅在PIIUser 或上 LoyaltyUserDetail
pswg

Ef抛出相同的异常。
伊尔卡伊İlknur

1
@pswg为什么在PIIUser使用FK?LoyaltyUserDetail具有FK,而不是PIIUser。因此它必须是LoyaltyUserDetail的PIIUserId属性上的[Key,ForeignKey(“ PIIUser”))。试试这个
user808128 2015年

@ user808128可以将注释放在导航属性或ID上,没有区别。
Thomas Boby

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);

可以做到这一点,被接受的解决方案无法清楚地解释这一点,这使我离开了几个小时才找到原因


1

这对于原始海报没有用,但是对于仍在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属性的三种方法,但没有一种起作用)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.