如何用实体框架在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 ?

另外,WithOptionalDependentWithOptionalPrincipal的实际区别是什么?-我在MSDN上读过,但是,我真的不明白其中的区别。

如何用实体框架在FluentAPI/数据注释中定义外键可选关系?

不允许为空的原因如下:

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自动成为关系中的主体,而PostPoll成为从属关系。主体在关系中拥有主键,依赖于外键,外键同时也是Post/Poll表中的主键,因为它是一对一的关系。只有在一对多关系中,外键才会有单独的列。对于一对一关系,您还必须删除外键列PostIdPollId,因为Posts通过其主键引用PostPoll

另一种似乎适合于您的模型的方法是继承映射。然后模型看起来像这样:

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