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

Blazor嵌套类/表数据读取展示及EF+SQLite设计合理性咨询

你当前的设计搭建了章节与段落的多对多关联基础,但存在几个关键完善点,同时百万级数据场景下需要针对性优化,下面分模块说明:

数据库设计合理性分析

可取之处

  • 采用多对多关联表SectionParagraph连接Section与Paragraph,符合章节可包含多个段落、段落可归属多个章节的业务逻辑,复合主键的配置也正确。
  • 实体类基础字段设计简洁,满足核心数据存储需求。

需要完善的点

  1. 缺失导航属性配置

    • Section类中未定义ICollection<SectionParagraph>导航属性,但OnModelCreating中使用了WithMany(e => e.SectionParagraphs),这会导致EF Core映射报错,需补充:
      public class Section
      {
          [Key]
          public int Id { get; set; }
          public string Name { get; set; }
          public ICollection<SectionParagraph> SectionParagraphs { get; set; } = new List<SectionParagraph>();
      }
      
    • Paragraph类同样需要补充ICollection<SectionParagraph>导航属性,同时在OnModelCreating中完善关联配置:
      mb.Entity<SectionParagraph>()
        .HasOne<Paragraph>()
        .WithMany(p => p.SectionParagraphs)
        .HasForeignKey(sp => sp.ParagraphId);
      
    • 建议给SectionParagraph添加导航属性,方便EF Core加载关联数据:
      public class SectionParagraph
      {
          public int ParagraphId { get; set; }
          public int SectionId { get; set; }
          public Section Section { get; set; }
          public Paragraph Paragraph { get; set; }
      }
      
  2. 缺少Document实体
    你提到业务包含"文档"概念,但现有设计未体现,导致无法区分不同文档的章节与段落。需补充Document实体:

    public class Document
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public ICollection<Section> Sections { get; set; } = new List<Section>();
    }
    

    并在Section类中添加外键:

    public int DocumentId { get; set; }
    public Document Document { get; set; }
    

    这样才能完整支撑"文档-章节-段落"的层级结构。

  3. 段落顺序缺失
    当前关联表未定义段落的展示顺序,数据库查询SectionParagraph时无法保证固定顺序,需添加SortOrder字段控制章节内段落的展示顺序:

    public class SectionParagraph
    {
        public int ParagraphId { get; set; }
        public int SectionId { get; set; }
        public int SortOrder { get; set; } // 用于定义段落在章节中的顺序
        // 导航属性...
    }
    

    同时更新查询逻辑,确保按SortOrder排序获取段落。

百万级数据场景下的性能优化建议

数据库层优化

  1. SQLite适配

    • SQLite单文件特性在高并发场景下会有锁竞争问题,如果是服务器端部署(Blazor Server+Web API),建议考虑切换到SQL Server/PostgreSQL等数据库;如果是Blazor WASM客户端SQLite,需注意本地存储容量限制。
    • 给常用查询字段添加索引:
      • 若按文档查询章节,给Section.DocumentId加索引;
      • 给SectionParagraph.SortOrder加索引,确保按顺序查询的效率。
  2. EF Core查询优化

    • 避免全量加载:绝对禁止一次性将百万级Paragraph/Section/SectionParagraph数据加载到内存,采用分页查询或按需加载,比如每次只查询当前需要的下一段落,或批量加载100-500条关联数据到前端缓存。
    • 使用AsNoTracking:查询只读数据时添加.AsNoTracking(),关闭EF的变更跟踪,提升查询性能:
      return await _db.Sections.AsNoTracking().ToListAsync();
      
    • 投影查询:只返回需要的字段,减少数据传输量,比如:
      return await _db.SectionParagraphs
                      .Where(sp => sp.SectionId == sectionId)
                      .OrderBy(sp => sp.SortOrder)
                      .Select(sp => new { sp.ParagraphId, sp.SectionId })
                      .ToListAsync();
      

前端(Blazor)优化

  1. 按需加载数据

    • 不要在OnInitializedAsync中拉取所有数据,改为点击"Next"时调用API获取下一段落的完整信息(包括所属章节),或提前批量加载一批(如50条)关联数据,用完后再请求下一批。
    • 用字典缓存章节与段落信息,避免每次渲染时遍历列表查询:
      Dictionary<int, Section> _sectionDict;
      Dictionary<int, Paragraph> _paragraphDict;
      
      protected override async Task OnInitializedAsync()
      {
          var sections = await HttpClient.GetFromJsonAsync<List<Section>>(NavigationManager.BaseUri + "sections");
          _sectionDict = sections.ToDictionary(s => s.Id);
          var paragraphs = await HttpClient.GetFromJsonAsync<List<Paragraph>>(NavigationManager.BaseUri + "paragraphs");
          _paragraphDict = paragraphs.ToDictionary(p => p.Id);
          // 只加载当前文档的关联表数据,而非全部
          sectionParagraphs = await HttpClient.GetFromJsonAsync<List<SectionParagraph>>(NavigationManager.BaseUri + "sectionparagraphs?documentId=1");
          sectionParagraph = sectionParagraphs[0];
      }
      

    渲染时直接通过字典取值:

    <p><b>Section Id: @_sectionDict[sectionParagraph.SectionId].Id</b></p>
    <p><b>Section Name: @_sectionDict[sectionParagraph.SectionId].Name</b></p>
    
  2. 空值安全处理
    给字典取值添加空值判断,避免渲染报错:

    @{
        var section = _sectionDict.TryGetValue(sectionParagraph.SectionId, out var s) ? s : null;
        var paragraph = _paragraphDict.TryGetValue(sectionParagraph.ParagraphId, out var p) ? p : null;
    }
    @if(section != null && paragraph != null)
    {
        <p><b>Section Id: @section.Id</b></p>
        <p><b>Section Name: @section.Name</b></p>
        <p>ParagraphNumber: @paragraph.Id</p>
        <p>Paragraph Name: @paragraph.Name</p>
    }
    

API层优化

  • 新增按文档、章节、分页查询的接口,比如:
    [HttpGet("sectionparagraphs/{documentId}")]
    public async Task<ActionResult<List<SectionParagraph>>> GetSectionParagraphsByDocument(int documentId, int page = 1, int pageSize = 50)
    {
        return await _db.SectionParagraphs
                        .Where(sp => sp.Section.DocumentId == documentId)
                        .OrderBy(sp => sp.SortOrder)
                        .Skip((page-1)*pageSize)
                        .Take(pageSize)
                        .AsNoTracking()
                        .ToListAsync();
    }
    
现有代码的改进点
  1. SeedData数据插入顺序
    当前先插入SectionParagraphs再插入Sections和Paragraphs,会触发外键约束错误,需调整顺序:
    db.Sections.AddRange(sections);
    db.Paragraphs.AddRange(paragraphs);
    db.SaveChanges(); // 先保存主表数据
    db.SectionParagraphs.AddRange(sectionParagraphs);
    db.SaveChanges();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 01:40:27