内容关系Skill content-relationships

这个技能专注于在无头内容管理系统(CMS)中设计和实现内容项之间的关系,涵盖关系类型(如一对一、多对多、自引用和多项式参考)、参考完整性管理、内容选取器字段模式、关系加载策略(急切加载、显式加载和投影),以及API设计(REST模式和GraphQL)。适用于构建内容层次结构、双向链接、处理删除行为和孤儿检测等场景,帮助开发者优化内容管理和API性能。关键SEO关键词:内容关系、headless CMS、API设计、关系建模、参考完整性、内容选取器、加载策略。

架构设计 0 次安装 0 次浏览 更新于 3/11/2026

名称: 内容关系 描述: 用于设计内容项之间的引用关系、内容选取器字段、多对多关系或双向链接。涵盖关系类型、参考完整性、急切/延迟加载,以及无头CMS的关系API。 允许工具: 读取, 全局搜索, 查找, 任务, 技能

内容关系

在无头CMS架构中设计和实现内容项之间关系的指南。

何时使用此技能

  • 向内容类型添加内容选取器字段
  • 设计作者-文章关系
  • 实现相关内容功能
  • 构建内容层次结构(父/子页面)
  • 管理双向关系
  • 处理删除时的参考完整性

关系类型

一对多(父引用)

// 文章有一个作者
public class Article
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;

    // 作者的外键
    public Guid AuthorId { get; set; }
    public Author? Author { get; set; }
}

public class Author
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;

    // 导航属性(反向)
    public List<Article> Articles { get; set; } = new();
}

多对多(连接表)

// 文章有多个类别,类别有多个文章
public class Article
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public List<ArticleCategory> ArticleCategories { get; set; } = new();
}

public class Category
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public List<ArticleCategory> ArticleCategories { get; set; } = new();
}

public class ArticleCategory
{
    public Guid ArticleId { get; set; }
    public Article Article { get; set; } = null!;

    public Guid CategoryId { get; set; }
    public Category Category { get; set; } = null!;

    // 可选:关系元数据
    public int Order { get; set; }
    public bool IsPrimary { get; set; }
}

// EF Core 配置
modelBuilder.Entity<ArticleCategory>()
    .HasKey(ac => new { ac.ArticleId, ac.CategoryId });

自引用(层次结构)

// 页面层次结构
public class Page
{
    public Guid Id { get; set; }
    public string Title { get; set; } = string.Empty;

    public Guid? ParentId { get; set; }
    public Page? Parent { get; set; }
    public List<Page> Children { get; set; } = new();

    // 为高效查询计算路径
    public string Path { get; set; } = string.Empty;
    public int Depth { get; set; }
}

多项式引用(任意内容类型)

// 引用任何内容项
public class ContentReference
{
    public Guid ReferencingItemId { get; set; }
    public string ReferencingItemType { get; set; } = string.Empty;

    public Guid ReferencedItemId { get; set; }
    public string ReferencedItemType { get; set; } = string.Empty;

    public string RelationshipType { get; set; } = string.Empty; // “相关”、“特色”、“另请参阅”
    public int Order { get; set; }
}

// 用法:文章引用产品、页面或其他文章

内容选取器字段模式

通用内容选取器

public class ContentPickerField
{
    // 此选取器允许的内容类型
    public List<string> AllowedContentTypes { get; set; } = new();

    // 选定的内容项ID
    public List<Guid> ContentItemIds { get; set; } = new();

    // 最小/最大选择项
    public int? MinItems { get; set; }
    public int? MaxItems { get; set; }

    // 显示选项
    public bool ShowContentType { get; set; } = true;
    public string DisplayTemplate { get; set; } = "{Title}";
}

// 存储在JSON列中
public class ArticleExtensions
{
    public ContentPickerField? RelatedArticles { get; set; }
    public ContentPickerField? FeaturedProducts { get; set; }
}

用于API的解析引用

public class ContentPickerFieldDto
{
    public List<Guid> ContentItemIds { get; set; } = new();

    // 可选包含解析项
    public List<ContentItemSummary>? Items { get; set; }
}

public class ContentItemSummary
{
    public Guid Id { get; set; }
    public string ContentType { get; set; } = string.Empty;
    public string DisplayText { get; set; } = string.Empty;
    public string? Url { get; set; }
    public string? ThumbnailUrl { get; set; }
}

关系加载策略

急切加载

// 用初始查询加载关系
public async Task<Article?> GetArticleWithRelationsAsync(Guid id)
{
    return await _context.Articles
        .Include(a => a.Author)
        .Include(a => a.ArticleCategories)
            .ThenInclude(ac => ac.Category)
        .FirstOrDefaultAsync(a => a.Id == id);
}

显式加载

// 按需加载关系
public async Task LoadAuthorAsync(Article article)
{
    await _context.Entry(article)
        .Reference(a => a.Author)
        .LoadAsync();
}

public async Task LoadCategoriesAsync(Article article)
{
    await _context.Entry(article)
        .Collection(a => a.ArticleCategories)
        .Query()
        .Include(ac => ac.Category)
        .LoadAsync();
}

用于API的投影

// 仅加载响应所需的内容
public async Task<ArticleDto?> GetArticleDtoAsync(Guid id)
{
    return await _context.Articles
        .Where(a => a.Id == id)
        .Select(a => new ArticleDto
        {
            Id = a.Id,
            Title = a.Title,
            AuthorName = a.Author!.Name,
            Categories = a.ArticleCategories
                .Select(ac => ac.Category.Name)
                .ToList()
        })
        .FirstOrDefaultAsync();
}

双向关系

维护双向关系

public class RelatedContent
{
    public Guid SourceId { get; set; }
    public Guid TargetId { get; set; }
    public string RelationType { get; set; } = string.Empty;
    public bool IsBidirectional { get; set; }
}

public class ContentRelationshipService
{
    public async Task AddRelationshipAsync(
        Guid sourceId,
        Guid targetId,
        string relationType,
        bool bidirectional = true)
    {
        // 添加正向关系
        await _repository.AddAsync(new RelatedContent
        {
            SourceId = sourceId,
            TargetId = targetId,
            RelationType = relationType,
            IsBidirectional = bidirectional
        });

        // 如果双向,添加反向关系
        if (bidirectional)
        {
            await _repository.AddAsync(new RelatedContent
            {
                SourceId = targetId,
                TargetId = sourceId,
                RelationType = GetReverseType(relationType),
                IsBidirectional = true
            });
        }
    }

    private string GetReverseType(string type) => type switch
    {
        "parent-of" => "child-of",
        "child-of" => "parent-of",
        "references" => "referenced-by",
        _ => type // 对称关系如“related-to”
    };
}

参考完整性

删除行为

public enum ReferenceDeleteBehavior
{
    Restrict,    // 如果被引用,阻止删除
    Cascade,     // 删除引用项
    SetNull,     // 清除引用
    NoAction     // 留下孤儿(在应用中处理)
}

// EF Core 配置
modelBuilder.Entity<Article>()
    .HasOne(a => a.Author)
    .WithMany(a => a.Articles)
    .HasForeignKey(a => a.AuthorId)
    .OnDelete(DeleteBehavior.Restrict);

孤儿检测

public class OrphanDetectionService
{
    public async Task<List<ContentReference>> FindOrphanReferencesAsync()
    {
        // 查找目标已不存在的引用
        return await _context.ContentReferences
            .Where(r => !_context.ContentItems
                .Any(c => c.Id == r.ReferencedItemId))
            .ToListAsync();
    }

    public async Task<List<ContentItem>> FindUnreferencedContentAsync(
        string contentType)
    {
        // 查找未被任何内容引用的内容
        var referencedIds = await _context.ContentReferences
            .Where(r => r.ReferencedItemType == contentType)
            .Select(r => r.ReferencedItemId)
            .Distinct()
            .ToListAsync();

        return await _context.ContentItems
            .Where(c => c.ContentType == contentType)
            .Where(c => !referencedIds.Contains(c.Id))
            .ToListAsync();
    }
}

关系API设计

REST模式

# 在单个请求中包含相关项
GET /api/articles/{id}?include=author,categories

# 嵌套资源
GET /api/articles/{id}/author
GET /api/articles/{id}/categories
GET /api/authors/{id}/articles

# 关系管理
POST   /api/articles/{id}/relationships/categories
DELETE /api/articles/{id}/relationships/categories/{categoryId}
PUT    /api/articles/{id}/relationships/author

包含项的响应

{
  "data": {
    "id": "article-123",
    "type": "Article",
    "attributes": {
      "title": "My Article"
    },
    "relationships": {
      "author": {
        "data": { "id": "author-456", "type": "Author" }
      },
      "categories": {
        "data": [
          { "id": "cat-1", "type": "Category" },
          { "id": "cat-2", "type": "Category" }
        ]
      }
    }
  },
  "included": [
    {
      "id": "author-456",
      "type": "Author",
      "attributes": { "name": "Jane Doe" }
    },
    {
      "id": "cat-1",
      "type": "Category",
      "attributes": { "name": "Technology" }
    }
  ]
}

GraphQL关系

type Article {
  id: ID!
  title: String!
  author: Author!
  categories: [Category!]!
  relatedArticles(first: Int): [Article!]!
}

type Query {
  article(id: ID!): Article

  // 反向查找
  articlesByAuthor(authorId: ID!): [Article!]!
  articlesByCategory(categoryId: ID!): [Article!]!
}

相关技能

  • 内容类型建模 - 定义关系字段
  • 动态模式设计 - 在JSON中存储引用
  • 无头API设计 - 关系API端点