// ============================================================================ // 【文件说明】HwPortalSearchDoc.cs - 搜索索引实体类 // ============================================================================ // 这是一个 Entity Framework Core (EF Core) 的实体类,用于存储搜索索引数据。 // // 【什么是搜索索引?】 // 搜索索引是"读模型"(Read Model),用于优化搜索查询性能: // 1. 把分散在多个业务表的数据聚合到一个表 // 2. 预处理搜索内容(如拼接标题、正文) // 3. 建立索引加速查询 // // 【与 SqlSugar 实体的区别】 // - SqlSugar 实体(如 HwWeb):映射到业务表,用于 CRUD 操作 // - EF Core 实体(如 HwPortalSearchDoc):映射到索引表,用于搜索查询 // // 为什么搜索用 EF Core 而不是 SqlSugar? // 1. EF Core 的 LINQ 查询更强大 // 2. EF Core 对复杂查询的支持更好 // 3. 项目技术栈混合使用,各取所长 // // 【与 Java Spring Boot 的对比】 // Java Spring Data JPA: // @Entity // @Table(name = "hw_portal_search_doc") // @Index(name = "uk_doc_id", columnList = "doc_id", unique = true) // public class HwPortalSearchDoc { ... } // // EF Core: // [Table("hw_portal_search_doc")] // [Index(nameof(DocId), IsUnique = true)] // public class HwPortalSearchDoc { ... } // // 两者概念相同,只是注解/特性的写法不同。 // ============================================================================ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; namespace Admin.NET.Plugin.HwPortal; /// /// hw-portal 搜索索引实体。 /// /// 【设计模式 - CQRS(命令查询职责分离)】 /// 这个实体体现了 CQRS 的"读模型"概念: /// - 命令模型(HwWeb 等):负责增删改,保证数据一致性 /// - 查询模型(HwPortalSearchDoc):负责查询,优化读取性能 /// /// 好处: /// 1. 读写分离,各自优化 /// 2. 搜索失败不影响主业务 /// 3. 可以针对搜索场景做特殊处理 /// /// [Table("hw_portal_search_doc")] // 【EF Core 索引特性】 // [Index(nameof(DocId), IsUnique = true)] 创建唯一索引。 // nameof(DocId) 获取属性名称字符串,避免硬编码。 // // 对比 Java JPA: // Java: @Index(name = "uk_doc_id", columnList = "doc_id", unique = true) // C#: [Index(nameof(DocId), IsUnique = true)] // // C# 的 nameof 更安全,重构时编译器会检查。 [Index(nameof(DocId), IsUnique = true)] [Index(nameof(SourceType))] [Index(nameof(UpdatedAt))] public class HwPortalSearchDoc { /// /// 主键ID。 /// /// 【C# 语法知识点 - [Key] 特性】 /// [Key] 标记主键字段。 /// /// 对比 Java JPA: /// Java: @Id /// @GeneratedValue(strategy = GenerationType.IDENTITY) /// private Long id; /// /// EF Core 会自动识别名为 Id 或 {类名}Id 的属性为主键, /// 但显式标注 [Key] 更清晰。 /// /// [Key] public long Id { get; set; } /// /// 搜索文档唯一键。 /// /// 【业务说明】 /// DocId 是文档的唯一标识,格式通常是:{SourceType}_{BizId} /// 例如:hw_web_100, hw_product_200 /// /// 为什么需要 DocId? /// 1. 全局唯一:不同来源的文档可以区分 /// 2. 幂等更新:相同 DocId 的文档会被更新而不是重复插入 /// /// /// 【C# 语法知识点 - [Required] 和 [MaxLength] 特性】 /// [Required]:必填字段,不能为 null /// [MaxLength(128)]:最大长度 128 字符 /// /// 对比 Java JPA: /// Java: @Column(name = "doc_id", nullable = false, length = 128) /// @NotNull /// private String docId; /// /// [Required] [MaxLength(128)] public string DocId { get; set; } = string.Empty; /// /// 来源类型(如 hw_web, hw_product 等)。 /// [Required] [MaxLength(32)] public string SourceType { get; set; } = string.Empty; /// /// 业务主键(如 web_id, product_info_id 等)。 /// [MaxLength(64)] public string BizId { get; set; } /// /// 搜索标题。 /// [MaxLength(500)] public string Title { get; set; } /// /// 搜索正文内容。 /// /// 【EF Core 列类型映射】 /// Content 字段在 DbContext.OnModelCreating 中被映射为 longtext: /// entity.Property(x => x.Content).HasColumnType("longtext"); /// /// 这是因为搜索内容可能很长,需要大文本类型。 /// /// public string Content { get; set; } /// /// 页面编码(用于跳转到详情页)。 /// [MaxLength(64)] public string WebCode { get; set; } /// /// 类型ID。 /// [MaxLength(64)] public string TypeId { get; set; } /// /// 设备ID。 /// [MaxLength(64)] public string DeviceId { get; set; } /// /// 菜单ID。 /// [MaxLength(64)] public string MenuId { get; set; } /// /// 文档ID。 /// [MaxLength(64)] public string DocumentId { get; set; } /// /// 基础分值(用于排序)。 /// public int BaseScore { get; set; } /// /// 前台路由(用户点击搜索结果跳转的页面)。 /// [MaxLength(255)] public string Route { get; set; } /// /// 前台路由参数(JSON 格式)。 /// /// 【JSON 列类型】 /// 在 DbContext 中映射为 json 类型: /// entity.Property(x => x.RouteQueryJson).HasColumnType("json"); /// /// MySQL 5.7+ 支持 JSON 类型,可以直接存储 JSON 数据。 /// /// public string RouteQueryJson { get; set; } /// /// 编辑路由(后台编辑页面的路由)。 /// [MaxLength(255)] public string EditRoute { get; set; } /// /// 逻辑删除标记。 /// /// 【逻辑删除】 /// "0":正常 /// "1":已删除 /// /// 搜索索引也需要逻辑删除,因为: /// 1. 删除业务数据时,索引也要标记删除 /// 2. 可以保留历史记录用于审计 /// /// [MaxLength(1)] public string IsDelete { get; set; } = "0"; /// /// 业务更新时间(来源数据的更新时间)。 /// public DateTime? UpdatedAt { get; set; } /// /// 索引创建时间。 /// public DateTime CreatedAt { get; set; } /// /// 索引更新时间。 /// public DateTime ModifiedAt { get; set; } }