I'm trying to make a simple Social Network (MicroBlog) with ASP.NET Core 5+ with Entity Framework Core. I ran into a problem when modeling the friendships between the users and fetching friends of a user.
While there are a lot of guides to modeling the entity as a Many-to-Many relation, for example, Self Referencing Many-to-Many relations gives a guide, link the Relation entity to the Ticket entity with two Relation's collection ICollection<Relation> RelatedTo and ICollection<Relation> RelatedFrom in Ticket, I'm wondering that if the relationship can be expressed more directly.
More specifically, My BlogUser and BlogUserFriendship is defined as follow:
public class BlogUser : IdentityUser
{
public virtual ICollection<Blog> Blogs { get; set; }
public virtual ICollection<BlogUser> Friends { get; set; }
public virtual ICollection<BlogUserFriendship> Friendships { get; set; }
}
public class BlogUserFriendship
{
public string User1Id { get; set; }
public virtual BlogUser User1 { get; set; }
public string User2Id { get; set; }
public virtual BlogUser User2 { get; set; }
}
I follow the instructions in Many-to-many self referencing relationship to model the relations in OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<BlogUser>()
.HasMany(u => u.Friends)
.WithMany(u => u.Friends)
.UsingEntity<BlogUserFriendship>(
j => j
.HasOne(fs => fs.User2)
.WithMany()
.HasForeignKey(fs => fs.User2Id)
.OnDelete(DeleteBehavior.Restrict),
j => j
.HasOne(fs => fs.User1)
.WithMany(u => u.Friendships)
.HasForeignKey(fs => fs.User1Id),
j =>
{
j.HasKey(fs => new { fs.User1Id, fs.User2Id });
}
);
}
I'm wondering if I can access one or more friends of a BlogUser by just accessing its Friends property. Or I can add another user to a user's Friends as simple as follow:
// Inside an ASP.NET Core Controller.
// The BlogUser is a subclass of IdentityUser
var user = userManager.GetUserAsync(this.User);
var friendToAdd = userManager.FindByIdAsync(model.UserId);
user.Friends.Add(friendToAdd);
await _userManager.UpdateAsync(user);
I tried the codes above, but it ran into a problem as follow:
System.InvalidOperationException: Unable to track an entity of type 'BlogUserFriendship' because its primary key property 'User1Id' is null.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
......
What is the problems in the codes above?
What's more, I finally make it works with following codes, where User1 is the current user (for example, let's say UserName is Alice), and User2 is another user (let's say UserName is Bob):
_context.BlogUserFriendship.Add(new BlogUserFriendship
{
User1 = user,
User1Id = user.Id,
User2 = friendToAdd,
User2Id = friendToAdd.Id
});
await _context.SaveChangesAsync();
And the table in database succeed in showing up a friendship record, Column User1Id is Alice's UserId, and Column User2Id is Bob's UserId, correctly.
But when I try to fetch usernames of all friends of Alice as follow:
// Inside a Controller
[Route("Friends")]
[HttpGet]
public async Task<IActionResult> GetFriends()
{
var user = await _userManager.GetUserAsync(this.User);
return Ok(user.Friends.Select(f => new {f.UserName}).ToList());
}
The result is an empty JSON array: []. And when I put a breakpoint to debug, I saw the Alice user's Friend property is empty (Count = 0, not a null object).
I also tried to swap the User1 and User2 in OnModelCreating:
builder.Entity<BlogUser>()
.HasMany(u => u.Friends)
.WithMany(u => u.Friends)
.UsingEntity<BlogUserFriendship>(
j => j
.HasOne(fs => fs.User1)
.WithMany()
.HasForeignKey(fs => fs.User1Id)
.OnDelete(DeleteBehavior.Restrict),
j => j
.HasOne(fs => fs.User2)
.WithMany(u => u.Friendships)
.HasForeignKey(fs => fs.User2Id),
j =>
{
j.HasKey(fs => new { fs.User1Id, fs.User2Id });
}
);
Then I receive a non empty JSON array, but contains only one user, Alice, who is the one I set to User1 when add the relationship record. Alice's friend, Bob, was not in the array:
[{"userName":"Alice"}]
When debugging, Alice became herself's friend, shows up in her Friends Collection.
I think there must be some error in my relationship model, but I can't find out what's wrong with it, or how to make it correctly.
Do you have any solutions? Or any better ideas? Thank you for your anwsering.