如何使用EF 7编写高效ValueComparer及优化现有转换器
现有转换器的正确性与性能分析
正确性
现有转换器功能上可正常工作,能完成Attachment与JSON字符串的互转以实现数据持久化,但存在几个潜在的正确性风险:
- 依赖
JsonSerializerOptions.Default全局配置,若后续该配置被修改(比如新增忽略null属性、调整属性序列化顺序),会导致序列化结果不一致,进而破坏相等比较逻辑或引发反序列化异常。 - 对于
Notes这类可空属性,若序列化时省略null字段(取决于配置),会导致逻辑相等的两个Attachment实例(仅Notes为null)序列化后的字符串不同,触发错误的不等判断。
性能
性能表现很差,核心问题在于所有操作(转换、相等比较、哈希计算、实例复制)都依赖完整的JSON序列化/反序列化:
- 序列化/反序列化会产生大量内存分配,频繁操作时会给GC带来压力。
- 每次比较或计算哈希都要重复序列化,存在大量冗余的性能开销。
不依赖序列化的高效实现方案
我们可以直接基于Attachment的属性编写转换与比较逻辑,完全规避序列化的开销,具体实现如下:
builder.Property(x => x.ScreenShot) .HasConversion( // 将Attachment转换为可存储的结构化字符串 attachment => { // 处理枚举与可空属性,转义分隔符避免拆分错误 var docType = attachment.DocumentType.ToString(); var notes = attachment.Notes ?? "NULL_MARKER"; // 转义分隔符|为\|,防止字段值中的|干扰拆分 var escapedDocId = attachment.DocumentId.Replace("|", "\\|"); var escapedDocType = docType.Replace("|", "\\|"); var escapedMimeType = attachment.MimeType.Replace("|", "\\|"); var escapedNotes = notes.Replace("|", "\\|"); return $"{escapedDocId}|{escapedDocType}|{escapedMimeType}|{escapedNotes}"; }, // 从存储字符串还原Attachment实例 storedString => { // 自定义拆分逻辑,处理转义的| var parts = new List<string>(); var currentPart = new StringBuilder(); bool isEscaped = false; foreach (char c in storedString) { if (isEscaped) { currentPart.Append(c); isEscaped = false; } else if (c == '\\') { isEscaped = true; } else if (c == '|') { parts.Add(currentPart.ToString()); currentPart.Clear(); } else { currentPart.Append(c); } } parts.Add(currentPart.ToString()); // 还原各属性,反转义分隔符,处理Notes的null标记 var docId = parts[0].Replace("\\|", "|"); var docType = Enum.Parse<DocumentType>(parts[1].Replace("\\|", "|")); var mimeType = parts[2].Replace("\\|", "|"); var notes = parts[3].Replace("\\|", "|") == "NULL_MARKER" ? null : parts[3].Replace("\\|", "|"); return new Attachment { DocumentId = docId, DocumentType = docType, MimeType = mimeType, Notes = notes }; }, // 自定义ValueComparer,直接基于属性进行比较与复制 new ValueComparer<Attachment>( (a1, a2) => { if (ReferenceEquals(a1, a2)) return true; if (a1 is null || a2 is null) return false; // 逐一比较所有属性 return a1.DocumentId == a2.DocumentId && a1.DocumentType == a2.DocumentType && a1.MimeType == a2.MimeType && a1.Notes == a2.Notes; }, attachment => { if (attachment is null) return 0; // 组合各属性的哈希值,避免重复计算 return HashCode.Combine(attachment.DocumentId, attachment.DocumentType, attachment.MimeType, attachment.Notes); }, attachment => { if (attachment is null) return null; // 直接复制属性创建新实例,比序列化反序列化高效得多 return new Attachment { DocumentId = attachment.DocumentId, DocumentType = attachment.DocumentType, MimeType = attachment.MimeType, Notes = attachment.Notes }; } ));
方案优势
- 性能大幅提升:所有操作直接针对属性,避免了JSON序列化的内存开销与计算成本,尤其在高频操作场景下差异明显。
- 逻辑可控:不依赖全局序列化配置,不会因配置变更引发意外问题。
- 内存分配优化:手动处理字符串拼接与拆分,内存分配更可控,减少GC压力。
内容的提问来源于stack exchange,提问作者Simple Code
相关产品推荐
相关产品推荐

