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.