You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化EF Core中含双用户引用的好友系统模型?

EF Core好友系统模型优化方案

我正在用EF Core设计好友系统,参考了Stack Exchange上的数据库结构方案,但当前模型因双用户引用带来不少复杂度:

Friendship类同时关联请求方和接收方用户:

public sealed class Friendship : EntityBase
{
    public User Requester { get; private set; } = null!;

    public User Addressee { get; private set; } = null!;

    // 其他无关属性、构造函数或方法...
}

这导致User类必须维护两个导航属性才能让EF Core正确映射:

public sealed class User : IdentityUser<Guid>
{
    private readonly List<Friendship> _friendshipsReceived = new();

    public IReadOnlyCollection<Friendship> FriendshipsReceived => _friendshipsReceived.AsReadOnly();

    private readonly List<Friendship> _friendshipsRequested = new();

    public IReadOnlyCollection<Friendship> FriendshipsRequested => _friendshipsRequested.AsReadOnly();

    // 其他无关属性、构造函数或方法...
}

这种设计让业务逻辑变得繁琐,比如筛选已通过的好友时,必须合并两个列表再处理:

var result = _friendshipsReceived
    .Concat(FriendshipsRequested)
    .Where(f => f.GetLatestFriendshipStatus().Equals(FriendshipStatus.From(FriendshipStatusCode.Accepted)));

因为不管是发起的请求被对方接受,还是收到的请求被自己接受,都得合并两个列表才能准确获取已确认的好友关系,现在需要更简洁的替代实现方案。


方案一:在User类中封装合并后的逻辑

不用修改数据库结构,直接在User类里封装合并和筛选逻辑,避免重复编写Concat代码:

public sealed class User : IdentityUser<Guid>
{
    // 原有的两个私有集合和导航属性...

    // 所有参与的好友关系
    public IReadOnlyCollection<Friendship> AllFriendships => 
        _friendshipsReceived.Concat(_friendshipsRequested).ToList().AsReadOnly();

    // 直接获取已通过的好友关系
    public IReadOnlyCollection<Friendship> AcceptedFriendships =>
        AllFriendships.Where(f => f.GetLatestFriendshipStatus().StatusCode == FriendshipStatusCode.Accepted)
                      .ToList().AsReadOnly();
}

业务层直接调用user.AcceptedFriendships就能拿到结果,不用每次手动合并列表。注意:如果是EF Core查询场景,这个属性仅做内存中处理,若要转为数据库查询,可在Repository层用投影实现类似逻辑。

方案二:用EF Core配置简化导航属性

调整EF Core的关系配置,让User类只保留一个Friendships导航属性,通过Fluent API指定双向外键关联:

首先修改User类:

public sealed class User : IdentityUser<Guid>
{
    private readonly List<Friendship> _friendships = new();
    public IReadOnlyCollection<Friendship> Friendships => _friendships.AsReadOnly();
}

然后在DbContext的OnModelCreating中配置关系:

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

    modelBuilder.Entity<Friendship>()
        .HasOne(f => f.Requester)
        .WithMany(u => u.Friendships)
        .HasForeignKey("RequesterId")
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<Friendship>()
        .HasOne(f => f.Addressee)
        .WithMany(u => u.Friendships)
        .HasForeignKey("AddresseeId")
        .OnDelete(DeleteBehavior.Restrict);

    // 可选:添加查询过滤,确保用户只能看到自己参与的好友关系
    modelBuilder.Entity<Friendship>()
        .HasQueryFilter(f => f.RequesterId == CurrentUserId || f.AddresseeId == CurrentUserId);
}

这里的CurrentUserId可从当前登录用户的上下文(比如Claim)中获取,配置后User的Friendships集合会自动包含所有自己发起或接收的好友关系,无需维护两个列表。

方案三:优化状态存储逻辑

如果核心痛点是筛选已接受的好友,可在Friendship类中直接存储最新状态,避免每次计算:

public sealed class Friendship : EntityBase
{
    public User Requester { get; private set; } = null!;
    public User Addressee { get; private set; } = null!;
    public FriendshipStatusCode LatestStatus { get; private set; }

    // 状态更新时同步更新LatestStatus
    public void UpdateStatus(FriendshipStatusCode newStatus)
    {
        LatestStatus = newStatus;
        // 状态历史记录逻辑(如需要)...
    }
}

筛选时直接用Where(f => f.LatestStatus == FriendshipStatusCode.Accepted),结合方案一或二,能进一步简化业务逻辑。


内容的提问来源于stack exchange,提问作者Maik Hasler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 13:33:17