如何用实体框架在FluentAPI/数据注释中定义外键可选关系?
本文关键字:定义 关系 注释 框架 实体 何用 FluentAPI 数据 | 更新日期: 2023-09-27 18:13:32
我有一个(示例)应用程序,代码如下:
public class Posts
{
[Key]
[Required]
public int ID { get; set; }
[Required]
public string TypeOfPost { get; set; }
public int PollID { get; set; }
public virtual Poll Poll { get; set; }
public int PostID { get; set; }
public virtual Post Post { get; set; }
}
基本上,我不知道是否有更好的方法来做到这一点,但是,我有一个帖子列表,并且,人们可以选择它是Poll
还是Post
,作为实体框架不与枚举一起工作,我只是将其存储为TypeOfPost
中的字符串,然后在应用程序中,我以编程方式查询基于TypeOfPost
的值的Poll或Post。
我不认为有设置"只需要一个"或类似的,所以,我处理所有的检查和应用程序中的东西。(如果有人知道更好的方法,请告诉我!)
无论如何,问题是,我可以通过进入SQL管理工作室并手动编辑模式以允许null来实现这一工作-但是,我只是不知道如何在FluentAPI中做到这一点,并需要一些帮助。
我已经尝试了以下两种方法:
modelBuilder.Entity<Post>()
.HasOptional(x => x.Poll).WithOptionalDependent();
modelBuilder.Entity<Post>()
.HasOptional(x => x.Poll).WithOptionalPrincipal();
第一个似乎在数据库中创建了一个允许null的额外列,第二个似乎没有做任何事情。
我相信第一个是我需要的,但是,我需要在Post
类中与[ForeignKey]结合使用它。如果我在这里是正确的,[ForeignKey]应该去虚拟财产,还是财产的ID ?
另外,WithOptionalDependent
和WithOptionalPrincipal
的实际区别是什么?-我在MSDN上读过,但是,我真的不明白其中的区别。
不允许为空的原因如下:
public int PollID { get; set; }
public virtual Poll Poll { get; set; }
public int PostID { get; set; }
public virtual Post Post { get; set; }
应该是
public int? PollID { get; set; }
public virtual Poll Poll { get; set; }
public int? PostID { get; set; }
public virtual Post Post { get; set; }
我可能会尝试创建两个一对一的关系可选:必需,因为Poll
必须有对Posts
的引用,Post
也必须有对Posts
的引用:
modelBuilder.Entity<Posts>()
.HasOptional(x => x.Post)
.WithRequired();
modelBuilder.Entity<Posts>()
.HasOptional(x => x.Poll)
.WithRequired();
这使得Posts
自动成为关系中的主体,而Post
或Poll
成为从属关系。主体在关系中拥有主键,依赖于外键,外键同时也是Post
/Poll
表中的主键,因为它是一对一的关系。只有在一对多关系中,外键才会有单独的列。对于一对一关系,您还必须删除外键列PostId
和PollId
,因为Posts
通过其主键引用Post
和Poll
。
另一种似乎适合于您的模型的方法是继承映射。然后模型看起来像这样:
public abstract class BasePost // your former Posts class
{
public int ID { get; set; }
public string UserName { get; set; }
}
public class Post : BasePost
{
public string Text { get; set; }
// other properties of the Post class
}
public class Poll : BasePost
{
// properties of the Poll class
}
您不再需要TypeOfPost
,因为您可以使用OfType
LINQ运算符过滤两个具体类型,例如:
var x = context.BasePosts.OfType<Post>()
.Where(p => p.UserName == "Jim")
.ToList();
这将选择特定用户的所有帖子,但不包括投票。
你必须决定你想要使用哪种类型的继承映射——TPH、TPT还是TPC。
编辑
要获得一对多关系,可以在Fluent API中指定以下映射:
modelBuilder.Entity<Posts>()
.HasOptional(x => x.Post)
.WithMany()
.HasForeignKey(x => x.PostID);
modelBuilder.Entity<Posts>()
.HasOptional(x => x.Poll)
.WithMany()
.HasForeignKey(x => x.PollID);
外键属性必须是可空的(int?
)。由于外键属性的命名遵循EF用于映射的命名约定,因此可以完全省略Fluent映射。只有当你有非常规的名字(如PostFK
或其他东西)时才需要它。然后,您也可以使用数据注释([ForeignKey(...)]
属性)而不是Fluent API。
ForeignKey必须为Nullable以使其成为可选的- virtual是单独的,并且只有在惰性加载时才需要。
声明式EF Code First中必需的关系:
public User User { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
声明式EF代码优先的可选关系:
public User User { get; set; }
[ForeignKey("User")]
public int? UserId { get; set; }
你会看到当你运行update-database -verbose -f
:
ALTER TABLE [dbo].[MyTable] ALTER COLUMN [UserId] [int] NULL
其他可能有帮助的东西。使用[Required]属性设置外键属性(注释)也将强制EF必需的导航属性,即使FK属性是可空的。我有一个遗留数据的特殊情况,其中需要FK属性,但可能引用也可能不引用关系中的记录。有道理,但我不认为英孚有这么"聪明"。
[Required] <-- even if the FK is nullable, OrgUnit will be Required
[StringLength(10)]
[ForeignKey("OrgUnit"), Column(Order = 1)]
public string OrgCode
{
get;
set;
}
... other FK, Column order 0
public virtual OrgUnit OrgUnit
{
get;
set;
}