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

时间:2011-11-05 11:53:24

标签: c# ef-code-first entity-framework-4.1

我有一个(示例)应用程序,其代码如下:

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 ,由于实体框架不能与Enums一起使用,我只是将其存储为TypeOfPost中的字符串,然后在应用程序中,我根据TypeOfPost的值以编程方式查询轮询或发布。

我认为无论如何设置“只需要一个”或类似的,所以,我处理应用程序中的所有检查和内容。 (如果有人知道更好的方法,请说出来!)。

无论如何,问题是,通过进入SQL Management Studio并手动编辑模式以允许空值,我可以正常工作 - 但是,我只是无法弄清楚如何在FluentAPI中执行此操作,并且需要一些帮助。

我尝试了以下两种方法:

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalDependent();

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalPrincipal();

第一个似乎在数据库中创建了一个允许空值的附加列,而第二个列似乎没有做任何事情。

我相信第一个是我需要的那个,但是,我需要在Post类中与[ForeignKey]结合使用它。如果我在这里是正确的,那么[ForeignKey]应该是虚拟财产还是财产的ID?

此外,WithOptionalDependentWithOptionalPrincipal之间的实际差异是什么? - 我在MSDN上看过,但是,我真的不明白它的区别。

4 个答案:

答案 0 :(得分:18)

之所以不允许空值,原因如下:

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

答案 1 :(得分:11)

我可能会尝试将两个一对一关系创建为可选:必需,因为Poll 必须引用{{1并且Posts 必须必须引用Post

Posts

这会使modelBuilder.Entity<Posts>() .HasOptional(x => x.Post) .WithRequired(); modelBuilder.Entity<Posts>() .HasOptional(x => x.Poll) .WithRequired(); 自动成为关系中的主体,PostsPost依赖。主体在关系中具有主键,依赖外键,它同时也是Poll / Post表中的主键,因为它是一对一的关系。只有在一对多关系中,您才能拥有外键的单独列。对于一对一关系,您还必须删除外键列PollPostId,因为PollId通过其主键引用Posts和{{1} }}

在您的模型中似乎合适的替代方法是继承映射。然后模型看起来像这样:

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 } LINQ运算符过滤这两种具体类型,例如:

TypeOfPost

这将选择特定用户的所有帖子,但不会选择民意调查。

您必须决定要使用哪种继承映射 - TPH, TPT or TPC

修改

要获得一对多关系,您可以在Fluent API中指定以下映射:

OfType

对于此,外键属性必须可以为空(var x = context.BasePosts.OfType<Post>() .Where(p => p.UserName == "Jim") .ToList(); ),如您所见。因为外键属性的命名遵循EF用于映射的命名约定,所以可以完全省略Fluent映射。只有你有非常规名称(如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?属性)代替Fluent API。

答案 2 :(得分:8)

ForeignKey必须是Nullable才能使其成为可选项 - 虚拟是独立的,只有延迟加载才需要。

声明性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

答案 3 :(得分:0)

其他可能有用的东西。使用[Required]属性设置外键属性(注释)也将强制执行EF所需的导航属性,即使FK属性可以为空。我有遗留数据的特殊情况,其中需要FK属性,但可能会或可能不会引用关系中的记录。有道理,但我不认为EF是“聪明”的。

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