'Why the special behaviour when FK identity is zero?

I have three tables:

UserProfiles (UserId is the primary key

UserId Email
------ ------------
9032   [email protected]

WebpagesRoles (RoleId is the primary key)

RoleId RoleName
------ ------------------
0      Site User
56     Admin

(Note the zero RoleId - that's the tricky bit.)

And WebpagesUsersInRoles, where both columns are foreign keys to the other tables.

I want to add these rows to WebpagesUsersInRoles

UserId RoleId
------ ------
9032   0
9032   56

My C#, EFCore 3.1 application has been deoptimised to aid investigation

        foreach (var uirToAdd in uirsToAdd)
        {
            _context.WebpagesUsersInRoles.Add(uirToAdd);
            // var role = _context.WebpagesRoles.FirstOrDefault(r => r.RoleId == uirToAdd.RoleId);
            // uirToAdd.Role = role;
            await _context.SaveChangesAsync(CancellationToken.None);
        }

The code snippet above can write the "Admin" row where RoleId is non-zero, but it can't write the "SiteUser" row where RoleId is zero. It fails on the SaveChangesAsync with the message 'The INSERT statement conflicted with the FOREIGN KEY constraint "fk_RoleId"'.

However, if I uncomment those two lines, it works fine.

Why?

Note, we are not using WebSecurity, just the tables, and so a command like roles.AddUserToRole("admin1", "Admin") is not appropriate. In any case, I'm just as curious about what's causing the problem as in finding a solution.

Edit

For reference, here's the actual model, including some parts I left out of the question for brevity:

public partial class WebpagesRole
{
    public WebpagesRole()
    {
        ActionDropdownRoles = new HashSet<ActionDropdownRole>();
        ActionTypeRoles = new HashSet<ActionTypeRole>();
        ClientTypeRoles = new HashSet<ClientTypeRole>();
        ScimUserGroupRoles = new HashSet<ScimUserGroupRole>();
        WebpagesUsersInRoles = new HashSet<WebpagesUsersInRole>();
    }

    public int RoleId { get; set; }
    public string RoleName { get; set; }
    public string Description { get; set; }

    public virtual ICollection<ActionDropdownRole> ActionDropdownRoles { get; set; }
    public virtual ICollection<ActionTypeRole> ActionTypeRoles { get; set; }
    public virtual ICollection<ClientTypeRole> ClientTypeRoles { get; set; }
    public virtual ICollection<ScimUserGroupRole> ScimUserGroupRoles { get; set; }
    public virtual ICollection<WebpagesUsersInRole> WebpagesUsersInRoles { get; set; }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source