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

如何设计可关联Question或Answer表的Comment表结构?

针对Comment实体需要二选一关联Question或Answer的场景,有三种经过生产验证的实现方案,可根据技术栈和业务需求选择:

方案1:多态外键(Stack Overflow 生产环境采用方案)

这是问答类系统最常用的实现方式,核心逻辑是给评论表增加两个字段,分别标记关联对象的类型、关联对象的主键值。

/// <summary>
/// 评论可关联的目标对象类型
/// </summary>
public enum CommentTargetType
{
    Question = 1,
    Answer = 2
}

class Comment
{
    public int Id { get; set; }
    public string Content { get; set; }
    // 标记当前评论归属的目标类型
    public CommentTargetType TargetType { get; set; }
    // 存储关联目标的主键值,Question/Answer的Id都存在该字段
    public int TargetId { get; set; }

    // 若使用EF Core这类支持导航属性的ORM,可配置条件导航属性
    // 注:两个导航属性同一时间仅会有一个有值
    public Question RelatedQuestion { get; set; }
    public Answer RelatedAnswer { get; set; }
}
  • 优点:表结构简单,查询性能高,后续扩展新的评论目标(比如专栏文章、用户动态)仅需新增枚举值,无需修改表结构。
  • 缺点:数据库层面无法直接创建外键约束保证关联数据的一致性,需要通过业务逻辑、触发器或校验框架保证TargetId对应的记录真实存在。

方案2:抽象基类/接口统一映射

让所有可被评论的实体实现同一个统一接口(或继承同一个实体基类),直接将Comment的关联属性定义为抽象类型,由ORM通过继承映射完成关联。

/// <summary>
/// 所有可被评论的实体统一实现该接口
/// </summary>
public interface ICommentTarget
{
    int Id { get; set; }
}

class Question : ICommentTarget
{
    public int Id { get; set; }
    public string Content { get; set; }
    public IEnumerable<Answer> Answers { get; set; }
    public IEnumerable<Comment> Comments { get; set; }
}

class Answer : ICommentTarget
{
    public int Id { get; set; }
    public string Content { get; set; }
    public Question Question { get; set; }
    public IEnumerable<Comment> Comments { get; set; }
}

class Comment
{
    public int Id { get; set; }
    public string Content { get; set; }
    public ICommentTarget For { get; set; }
}
  • 优点:代码层面符合面向对象设计原则,类型安全性高,EF Core 8.0+ 可原生通过TPH/TPT映射自动完成配置。
  • 缺点:如果使用TPT(每个具体实体对应独立表)映射,查询时会产生多表连接,性能略低于多态外键方案;新增评论目标类型时需要调整继承体系。

方案3:可空独立外键

在Comment表中直接添加两个可空外键字段,分别关联Question和Answer,通过业务逻辑约束两个字段同一时间有且仅有一个非空。

class Comment
{
    public int Id { get; set; }
    public string Content { get; set; }
    public int? QuestionId { get; set; }
    public Question Question { get; set; }
    public int? AnswerId { get; set; }
    public Answer Answer { get; set; }
}
  • 优点:数据库层面可以创建完整的外键约束,数据一致性由数据库层保证,不需要额外的类型标记字段。
  • 缺点:字段冗余,后续每新增一种可评论的对象类型都需要新增外键字段、修改表结构,查询时需要判断非空字段取关联对象,编码繁琐。

选型建议

做类Stack Overflow的问答场景优先选方案1,性能最好、扩展最灵活,已经过超大规模流量验证;如果项目强依赖数据库外键保证数据一致性,且后续不会频繁新增评论目标类型,可以选方案3;如果技术栈对ORM继承映射支持完善,偏好面向对象的编码风格,可以选方案2。


内容的提问来源于stack exchange,提问作者Display Name

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:03:27