I'm having trouble creating new entities with one-to-one relationships. EF Core is not assigning the foreign keys properly.
As an example, let's take two entities:
public class User
{
public int Id { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
public class Blog
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Define their relationship so that it's clear which is dependent on which:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>()
.HasOne(u => u.Blog)
.WithOne(b => b.User)
.HasForeignKey<Blog>()
.IsRequired();
}
Then when I try to create them both like so:
var user = new User
{
Blog = new Blog(),
};
await DbContext.Set<User>().AddAsync(user);
await DbContext.SaveChangesAsync();
The Blog
is created and its UserId
property is set correctly. However, the BlogId
property of User
is 0. According to the docs, it should be possible to create related entities like this.
Does this not apply to one-to-one relationships? Or is there something else I'm missing?
I'm using postgres btw, in case the DB provider has something to do with it. Also, EF Core version 9.
If a user can only have one blog, then a classic one-to-one relationship would actually look like:
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public Blog Blog { get; set; }
}
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public User User { get; set; }
}
// with...
builder.Entity<User>()
.HasOne(u => u.Blog)
.WithOne(b => b.User)
.IsRequired();
By default EF will associate both entities by their PKs, not needing a separate FK in either table. In a one to one relationship the two tables share the same PK. There is no point in them having different PKs since there will only ever be them as a pair. You can specify an alternate FK in either table, but one or the other, and it isn't required unless the relationship is bi-directionally optional. (We can have 0-1, 1-1, or 1-0 relationships between users and blogs) In that scenario you would want to use a dedicated FK on one or the other tables.
However, in this example it doesn't make sense that a User would only have a single Blog. Chances are they could have several. This would be a one-to-many relationship:
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ICollection<Blog> Blogs { get; } = [];
}
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
// with...
builder.Entity<User>()
.HasMany(u => u.Blogs)
.WithOne(b => b.User)
.HasForeignKey(b => b.UserId)
Here we can have a User record with 0 to any number of blogs, and the creation of a Blog requires being associated to a User.
In this case I would recommend using a Shadow Property for the UserId FK on the Blog table:
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ICollection<Blog> Blogs { get; } = [];
}
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public User User { get; set; }
}
// with...
builder.Entity<User>()
.HasMany(u => u.Blogs)
.WithOne(b => b.User)
.HasForeignKey("UserId")
This tells EF that there is a UserId column in the Blogs table to serve as the FK to the User without exposing a UserId property in the Blog entity. When it comes to bi-directional references it is generally better to avoid exposing both FK and navigation properties as this forms 2 sources of truth. If a navigation property is loaded/set, it takes precidence to the FK property, but setting the FK property when the navigation property is not set/loaded will take precidence. This can lead to bugs if some code checks/relies on blog.UserId while other code might use blog.User. Setting the FK does not change any loaded User reference. Setting the navigation property does not automatically update the FK until after SaveChanges()
is called. Usually it is safer to use one or the other, and be careful if there is a reason to use both.