如何设计可关联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
相关产品推荐
相关产品推荐

