Как создать функцию «Добавить друга» между двумя отдельными учетными записями пользователей в одной таблице в основной идентификации asp.net

Я разрабатываю мини-веб-приложение для социальных сетей и использую ASP.NET Identity для создания учетных записей пользователей и управления ими. Я хочу добавить другую учетную запись пользователя в качестве друга к моей учетной записи. Я мог бы успешно это сделать, но проблема в том, что когда я проверил учетную запись моего добавленного друга, в его списке друзей нет обновлений. Пусто.

Вот мой класс User, унаследованный от IdentityUser,

public class AppUser : IdentityUser
{
    public AppUser()
    {
        this.Friends = new HashSet<AppUser>();
    }
    public int Age { get; set; }
    public string Sex { get; set; }
    public string City { get; set; }
    public string Education { get; set; }
    public string Description { get; set; }
    public string? FriendOfUserId { get; set; }
    public virtual AppUser FriendOf { get; set; }

    public ICollection<AppUser> Friends { get; set; }
}

Мой класс DbContext,

public class ApplicationDbContext : IdentityDbContext<AppUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
builder.Entity<AppUser>(x =>
        {
            x
                .HasMany(x => x.Friends)
                .WithOne(x => x.FriendOf)
                .HasForeignKey(x => x.FriendOfUserId);
        });
    }
    public DbSet<AppUser> Users { get; set; }
}

Мой метод класса контроллера для добавления друга,

public async Task<IActionResult> AddFriend(string id)
    {
        var addFriend = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == id);
        var user = await userManager.GetUserAsync(this.User);
        var u = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == user.Id);
        user.FriendOf = addFriend;
        user.Friends.Add(addFriend);
        await context.SaveChangesAsync();
        return Redirect("/");
    }

person ithuRik    schedule 04.08.2020    source источник
comment
Основываясь на ответе Дэвида Линга и вашем обновлении исходного сообщения, я обновил свой ответ.   -  person Dan Barrett    schedule 05.08.2020
comment
@ithuRik: извините, что так долго. Я был очень занят на работе до сих пор. Я обновил свой ответ, чтобы отразить эту самореферентную связь «многие ко многим», а не просто «многие к одному», о которой я думал ранее. Пожалуйста, проверьте это.   -  person David Liang    schedule 08.08.2020


Ответы (2)


Я думаю, что вы неправильно моделируете свою сущность. Поскольку пользователь может иметь список друзей, а также быть другом других пользователей, я думаю, вам нужно зафиксировать последнюю часть в модели.

Поскольку это связь «многие ко многим», а EF Core до сих пор не поддерживает не объявляя объект для представления таблицы соединения, вам также необходимо определить этот объект :

public class AppUser : IdentityUser
{
    public int Age { get; set; }
    public string Sex { get; set; }
    public string City { get; set; }
    public string Education { get; set; }
    public string Description { get; set; }

    public ICollection<AppUserFriendship> FriendsOf { get; set; }
    public ICollection<AppUserFriendship> Friends { get; set; }
}

public class AppUserFriendship
{
    public string UserId { get; set; }
    public AppUser User { get; set; }

    public string UserFriendId { get; set; }
    public AppUser UserFriend { get; set; }
}

И затем вам нужно настроить их отношения:

public class ApplicationDbContext : IdentityDbContext<AppUser>
{
    ...

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<AppUserFriendship>(b =>
        {
            b.HasKey(x => new { x.UserId, x.UserFriendId };

            b.HasOne(x => x.User)
                .WithMany(x => x.Friends)
                .HasForeignKey(x => x.UserId)
                .OnDelete(DeleteBehavior.Restrict);

            b.HasOne(x => x.UserFriend)
                .WithMany(x => x.FriendsOf)
                .HasForeignKey(x => x.UserFriendId)
                .OnDelete(DeleteBehavior.Restrict);
        });
    }

    public DbSet<AppUser> Users { get; set; }
}

Заметка 3_. Вы должны установить для него значение, отличное от DeleteBehavior.Cascade, которое используется по умолчанию, чтобы предотвратить каскадное удаление.

Отказ от ответственности: я написал все от руки. Никогда не проверяйте это.

person David Liang    schedule 05.08.2020
comment
Я отредактировал свой код, как вы предложили, теперь я тоже вижу свое имя в учетной записи моего друга, но теперь у меня новая проблема. Когда я добавляю двух друзей, мое имя есть только в аккаунте последнего добавленного друга. это не проблема, когда я добавил только одного друга. Но когда я пытаюсь добавить двух или более друзей, возникает эта проблема. Я отредактировал свой метод контроллера выше. Вы можете это увидеть. - person ithuRik; 05.08.2020
comment
@ithuRik: Упс, я предполагал, что у тебя может быть список друзей, но ты можешь быть другом только одного человека. Вот почему я смоделировал FriendOf только как один ко многим. Если это действительно «многие ко многим», нам, возможно, придется создать промежуточную таблицу соединений. Я не уверен, поддерживаете ли вы сценарий, в котором вы мой друг, но я не ваш друг? - person David Liang; 06.08.2020

Вам нужно будет использовать функцию Include.

// What you have is fine.
var friend = context.Users.Select ( u => u == id );

// this is what needs to occur in await userManager.GetUserAsync(this.User);
// Without the include the Navigation Property will not be tracked.
var user = context.Users
    .Select ( u => u == id )
    .Include ( u => u.Friends );

user.Friends.Add ( friend );
context.SaveChanges ();

Проверьте загрузку связанных данных. https://docs.microsoft.com/en-us/ef/core/querying/related-data

РЕДАКТИРОВАТЬ:

Взгляните на этот пост, он дубликат этого.

отношения "многие ко многим", ссылающиеся на себя

РЕДАКТИРОВАТЬ 2: Итак, проблема, с которой вы столкнулись, заключается в том, что вы все еще пытаетесь смоделировать это как одну таблицу. Подумайте, как это будет структурировано в базе данных SQL. Как одна таблица может содержать коллекцию (Friend) в одном столбце? Для этого мы создадим новую таблицу для моделирования отношений между AppUsers.

public class Friendship 
{
    // Foreign Key
    Int32 MeId { get; set; }

    // Foreign Key
    Int32 FriendId { get; set; }

    // Navigation Property
    AppUser Me { get; set; }

    // Navigation Property
    AppUser Friend { get; set; }
}
public class AppUser : IdentityUser
{
    public AppUser()
    {
        this.Friends = new HashSet<AppUser>();
    }
    
    // Primary Key. Is this defined in IdentityUser?
    public int Id { get; }
    public int Age { get; set; }
    public string Sex { get; set; }
    public string City { get; set; }
    public string Education { get; set; }
    public string Description { get; set; }

    // This is considered a Navigation Property 
    public ICollection<Friendship> Friends { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.Entity<AppUser>(x =>
    {
        x.HasPrimaryKey ( x => x.Id );
    });

    builder.Entity<Friendship>( f =>
    {
        f.HasKey ( f => new { f.MeId, f.FriendId } );
        f
            .HasOne( f => f.Me )
            .WithMany ( u => u.Friends )
            .HasForeignKey( f => f.MeId );

        f
            .HasOne( f => f.Friend )
            .WithMany ( u => u.Friends )
            .HasForeignKey( f => f.FriendId );
    });        
}

На этом этапе вы должны быть в состоянии запросить таблицу соединений для дружбы.

public void AddFriend ( AppUser user, AppUser friend ) {
    var trackedUser = context.AppUsers
         .Select ( u => u.Id == user.Id )
         .Include ( u => u.Friends );          
         .FirstOrDefault ();

   trackedUser.Friends.Add ( new Friendship () { 
       MeId = user.Id, 
       FriendId = friend.Id 
   });
   context.SaveChanges ();
}
person Dan Barrett    schedule 04.08.2020
comment
Спасибо за помощь. Этот пост действительно помог мне понять, в чем проблема. Но когда я пытаюсь это сделать в своем коде, он показывает ошибку Для типа сущности "Друзья" требуется определить первичный ключ. Если вы намеревались использовать тип объекта без ключа, вызовите «HasNoKey()». - person ithuRik; 05.08.2020
comment
Теперь я исправил ошибку первичного ключа, добавив свойство HasKey в построитель моделей, но я все еще не мог обновить свое имя в списке друзей. Я думаю, что что-то не так с моими методами контроллера. Уже неделю мучаюсь с этой проблемой и до сих пор не нашел решения. - person ithuRik; 05.08.2020