Is it possible in EF Core to make a one-way navigation property required?

Viewed 746

I am working on a basic group chat system, for which I created these classes:

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

We are using EF Core 3.1 Code-First with migrations.

I am looking for a way to make Message.Author a required property, which should lead to a column in table Message that is created as AuthorId NOT NULL.

I tried:

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

As this is applied using Add-Migration and Update-Database, the database column AuthorId is created, but with NULLs allowed.

There does not seem to be a method IsRequired() that I can add after HasOne().

I also tried:

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

but that fails saying

The property 'Message.Author' is of type 'Role' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

Doing .HasOne(...) followed by .Property(...).IsRequired() also does not work:

'Author' cannot be used as a property on entity type 'Message' because it is configured as a navigation.

I managed to make Message.Conversation required through this:

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
}

However I'd rather not make Role aware of Messages, as I will never want to retrieve Messages directly from a Role (this will happen through Conversations and Participants).

My ultimate question is: Is there a way to make Message.Author required (NOT NULL), without linking Message and Role together in a full 1-to-many relationship with a Messages property in Role?

2 Answers

What about adding Role's foreign key to Message and then requiring that property to not be null? Something like:

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

While the answer by @Ben Sampica was helpful and got me where I needed to be, the comments by @Ivan Stoev provided details and clarity that made me think that a more comprehensive answer would be useful.

There are multiple ways to make a foreign key column required (NOT NULL) in the generated table.

  1. The simplest is to put [Required] on the navigation property:

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

    This will cause EF to create a shadow property AuthorId of type Guid because Message.Author is a Role and Role.Id is of type Guid. This leads to UNIQUEIDENTIFIER NOT NULL in case of SQL Server.

    If you omit [Required] then EF will use Guid?, which leads to UNIQUEIDENTIFIER NULL, unless you apply one of the other options.

  2. You can use an explicit Id property with a type that can't be null:

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

    Note (i) - This only works if you follow EF Core shadow property naming rules, which in this case means you must name the Id property nameof(Author) + nameof(Role.Id) == AuthorId.
    Note (ii) - This will break if one day you decide to rename Author or Role.Id but forget to rename AuthorId accordingly.

  3. If you can't or don't want to change the Model class, then you can tell EF Core that it needs to treat the shadow property as required:

    builder.Property("AuthorId").IsRequired();
    

    The same Notes apply as listed at 2, with the addition that you could now use nameof() to reduce the effort and the risks.


In the end I decided to use the [Required] approach, because

  • It is simple and descriptive,
  • No effort needed to think of which shadow property name to use,
  • No risk of breaking the shadow property name later on.

This may apply sometimes, not always:

  • Input forms may use the Model class attribute to check if a property is required. However it may be a better approach to build your forms around DTO classes, and then an attribute on an EF Model class may provide no worth for your forms.
Related