如何使用entity framework在FluentAPI /数据注释中定义外键可选关系?
我有一个(示例)应用程序,代码如下:
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; } }
基本上,我不知道是否有更好的方法来做到这一点,但是,我有一个post列表,并且,人们可以选择是否是Poll
或Post
,因为entity framework不适用于Enums,我只是将它存储为TypeOfPost
的字符串,然后在应用程序中,我根据TypeOfPost
的值以编程方式查询Poll或Post。
我认为无论如何设置“只需要一个”或类似,所以,我处理应用程序中的所有检查和内容。 (如果有人知道更好的方法,请说出来!)。
无论如何,问题是,通过进入SQL Management Studio并手动编辑模式以允许空值,我可以正常工作 – 但是,我只是无法弄清楚如何在FluentAPI中执行此操作,并需要一些帮助。
我尝试了以下两种方法:
modelBuilder.Entity() .HasOptional(x => x.Poll).WithOptionalDependent(); modelBuilder.Entity() .HasOptional(x => x.Poll).WithOptionalPrincipal();
第一个似乎在数据库中创建了一个允许空值的附加列,而第二个列似乎没有做任何事情。
我相信第一个是我需要的,但是,我需要在Post
Class中与[ForeignKey]结合使用它。 如果我在这里是正确的,那么[ForeignKey]应该是虚拟财产还是财产的ID?
另外, WithOptionalDependent
和WithOptionalPrincipal
之间的实际区别是什么? – 我在MSDN上看过,但是,我真的不明白它的区别。
我可能会尝试将两个一对一关系创建为可选:必需,因为Poll
必须具有对Posts
的引用,而Post
也必须引用Posts
:
modelBuilder.Entity() .HasOptional(x => x.Post) .WithRequired(); modelBuilder.Entity () .HasOptional(x => x.Poll) .WithRequired();
这使得Posts
自动成为关系中的主体并Post
或Poll
依赖关系。 主体在关系中具有主键,在Post
/ Poll
表中依赖外键也是主键,因为它是一对一的关系。 只有在一对多关系中,您才能拥有一个单独的外键列。 对于一对一的关系,您还必须删除外键列PostId
和PollId
因为Posts
通过其主键引用Post
和Poll
。
在您的模型中似乎合适的替代方法是inheritance映射。 然后模型看起来像这样:
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() .Where(p => p.UserName == "Jim") .ToList();
这将选择特定用户的所有post,但不会选择民意调查。
您必须决定使用哪种inheritance映射 – TPH,TPT或TPC 。
编辑
要获得一对多关系,您可以在Fluent API中指定以下映射:
modelBuilder.Entity() .HasOptional(x => x.Post) .WithMany() .HasForeignKey(x => x.PostID); modelBuilder.Entity () .HasOptional(x => x.Poll) .WithMany() .HasForeignKey(x => x.PollID);
正如您已经找到的那样,外键属性必须是可为空的( int?
)。 因为外键属性的命名遵循EF用于映射的命名约定,所以可以完全省略Fluent映射。 只有你有非常规名称(如PostFK
或其他东西)才需要它。 然后,您还可以使用数据注释( [ForeignKey(...)]
属性)而不是Fluent API。
之所以不允许空值,原因如下:
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; }
ForeignKey必须是Nullable才能使其成为可选项 – 虚拟是独立的,只有延迟加载才需要。
声明性EF Code First中的必需关系:
public User User { get; set; } [ForeignKey("User")] public int UserId { get; set; }
声明性EF Code First中的可选关系:
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属性,但可能会或可能不会引用关系中的记录。 有道理,但我不认为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; }