使用EntityFrameworkFluent API的一对一可选关系

本文关键字:关系 一对一 EntityFrameworkFluent API 使用 | 更新日期: 2023-09-27 18:27:46

我们希望使用实体框架代码优先使用一对一的可选关系。我们有两个实体。

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创建一对一的可选关系?

使用EntityFrameworkFluent API的一对一可选关系

EF Code First支持1:11:0..1关系。后者就是你想要的("一比零或一")。

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

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

这里有一个来自编程E.F.代码第一本书的例子

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的CCD_ 21属性是可选的并且LoyaltyUser的PIIUser属性是必需的。

您必须始终使用HAS/WITH模式。

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

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

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

EF应该创建所有你需要的外键,只是不在乎WithMany!

您的代码有几个问题。

1:1关系是:PK&lt-PK,其中一个PK侧也是FK,或者PK&lt-FK+UC,其中FK侧是非PK且具有UC。您的代码显示您有FK&lt-FK,因为您将两侧定义为具有FK,但这是错误的。I recon PIIUser是PK侧并且CCD_ 26是FK侧。这意味着PIIUser没有FK字段,但LoyaltyUserDetail有。

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

上面的p.s.w.g.确实回答了你的问题,但犯了一个错误,他/她也在PIIUser中定义了FK,这当然是我上面描述的错误因此在LoyaltyUserDetail中定义可为null的FK字段,在LoyaltyUserDetail中定义属性以将其标记为FK字段;但不要在PIIUser中指定FK字段

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

EF在1:1时不是很好,因为它不能处理唯一的约束我不是代码优先的专家,所以我不知道它是否能够创建UC。

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

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

这将解决REFERENCE外键上的问题

更新删除记录

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

这对原始海报没有用处,但对于仍在使用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属性的方法,但都不起作用)。

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

如果成功的话,接受的解决方案无法清楚地解释这一点,我花了几个小时才找到

的原因