使用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创建一对一的可选关系?
EF Code First支持1:1
和1:0..1
关系。后者就是你想要的("一比零或一")。
在一种情况下,您尝试流利地说两端都需要,而在另一种情况中,两端都是可选的。
您需要的是一端的可选,另一端的必需。
这里有一个来自编程E.F.代码第一本书的例子
modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);
PersonPhoto
实体具有名为PhotoOf
的导航属性,该属性指向Person
类型。Person
类型具有名为Photo
的导航属性,该属性指向PersonPhoto
类型。
在两个相关的类中,使用每种类型的主键,而不是外键。即,您不会使用LoyaltyUserDetailId
或PIIUserId
属性。相反,关系取决于这两种类型的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,一对一(或一对零/一)关系是首先在代码中配置的最令人困惑的关系之一,因此您并不孤单!:)
就像LoyaltyUserDetail
和PIIUser
之间有一对多关系一样,所以映射应该是
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(m => m.PIIUser )
.WithMany()
.HasForeignKey(c => c.LoyaltyUserDetailId);
EF应该创建所有你需要的外键,只是不在乎WithMany!
您的代码有几个问题。
1:1关系是:PK<-PK,其中一个PK侧也是FK,或者PK<-FK+UC,其中FK侧是非PK且具有UC。您的代码显示您有FK<-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);
如果成功的话,接受的解决方案无法清楚地解释这一点,我花了几个小时才找到