在EF Core中是否可能需要单向导航属性?

问题描述

我正在开发一个基本的群聊系统,为此我创建了这些类:

public class Role
{
    public Guid Id { get; set; };
    public string Username { get; set; }
}

public class Message
{
    public Guid Id { get; set; };
    public Role Author { get; set; }
    public Conversation Conversation { get; set; }
    public DateTime Date { get; set; }
    public string Text { get; set; }
}

public class Conversation
{
    public Guid Id { get; set; };
    public IList<ConversationParticipant> ConversationParticipants { get; set; };
    public IList<Message> Messages { get; set; };
}

public class ConversationParticipant
{
    public Conversation Conversation { get; set; }
    public Role Role { get; set; }
}

我们正在使用EF Core 3.1 Code-First进行迁移。

我正在寻找一种使Message.Author成为必需属性的方法,该属性应导致在表Message中创建的一列为AuthorId NOT NULL

我尝试过:

public static void Map(this EntityTypeBuilder<Message> builder)
{
    builder.HasOne(m => m.Author);
}

由于这是使用“添加迁移”和“更新数据库”应用的,因此将创建数据库列AuthorId,但允许NULL

似乎没有可以在IsRequired()之后添加的方法HasOne()

我也尝试过:

public static void Map(this EntityTypeBuilder<Message> builder)
{
    builder.Property(m => m.Author).IsRequired();
}

但这没说

属性“ Message.Author”的类型为“角色”,当前数据库提供程序不支持。

要么更改属性CLR类型,要么使用“ [NotMapped]”属性或在“ OnModelCreating”中使用“ EntityTypeBuilder.Ignore”忽略该属性。

先进行.HasOne(...)后再进行.Property(...).IsRequired()也是行不通的:

“作者”不能用作实体类型“消息”的属性,因为它已配置为导航。

我设法通过此操作使Message.Conversation成为必需项:

public static void Map(this EntityTypeBuilder<Conversation> builder)
{
    builder.HasMany(c => c.Messages)       // A conversation can have many messages
           .WithOne(e => e.Conversation)   // Each message belongs to at most 1 conversation
           .IsRequired();                  // A message always has a conversation
}

但是我不想让Role注意到消息,因为我永远不想直接从角色检索消息(这将通过对话和参与者进行)。

我的最终问题是:是否有一种方法可以使Message.Author为必需(不为NULL),而无需将MessageRole链接成一个完整的一对多关系Role中的Messages属性?

解决方法

如何将Role的外键添加到Message,然后要求该属性不为null?像这样:

// MessageConfiguration.cs
builder.Property(b => b.RoleId).IsRequired()
,

虽然@Ben Sampica的回答很有帮助,并让我找到了需要的地方,但@Ivan Stoev的评论却提供了细节和清晰度,使我认为更全面的回答将是有用的。

有多种方法可以在生成的表中使外键列成为必需(NOT NULL)。

  1. 最简单的方法是将[Required]放在导航属性上:

    public class Message
    {
        // ...
        [Required] public Role Author { get; set; }
        // ...
    }
    

    这将导致EF创建AuthorId类型的阴影属性Guid,因为Message.Author是Role角色,而Role.Id是Guid类型。对于SQL Server,这将导致UNIQUEIDENTIFIER NOT NULL

    如果您省略[Required],那么EF将使用Guid?,这将导致UNIQUEIDENTIFIER NULL,除非您应用其他选项之一。

  2. 您可以使用类型不能为null的显式Id属性:

    public class Message
    {
        // ...
        public Guid AuthorId { get; set; }
        public Role Author { get; set; }
        // ...
    }
    

    注释(i)-仅在遵循EF Core shadow property naming rules的情况下有效,在这种情况下,这意味着您必须命名Id属性nameof(Author) + nameof(Role.Id) == {{1 }}。
    注释(ii)-如果有一天您决定重命名AuthorIdAuthor但忘记相应地重命名Role.Id,则此操作将中断。

  3. 如果您不能或不想更改Model类,则可以告诉EF Core它需要根据需要处理shadow属性:

    AuthorId

    2 中列出的相同的注释适用,此外,您现在可以使用builder.Property("AuthorId").IsRequired(); 来减少工作量和降低风险。


最后,我决定使用nameof()方法,因为

  • 它简单而具有描述性,
  • 无需考虑要使用哪个阴影属性名称,
  • 以后没有打破影子属性名称的风险。

这有时可能适用,但并不总是如此:

  • 输入表单可以使用Model类属性来检查是否需要属性。但是,用 DTO类构建表单可能是一种更好的方法,然后 EF Model类上的属性可能对您的表单毫无价值。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...