C# 重写Equals仅对比Id场景下List无法添加重复Allegato对象问题问询
问题根源
该问题由两个共同作用的原因导致:
- 未持久化的
Allegato对象Id均为int类型默认值0,BaseObject中定义的Equals方法会判定所有未持久化的Allegato对象相等,配套的GetHashCode方法返回值也完全相同。 - NHibernate默认会将实体类中的
ICollection类型属性按Set策略映射,运行时会将初始化的List实例替换为HashSet代理实现。HashSet添加元素时会自动调用Equals和GetHashCode做去重校验,因此只有第一个元素会被成功添加,后续被判定为相等的元素都会被直接丢弃。
解决方案
以下方案均不需要修改原有NHibernate基础设施逻辑:
方案1:重写Allegato的相等判定逻辑(最优)
BaseObject中的BaseObjectEquals和GetHashCode方法均为virtual修饰,可以在Allegato子类中重写,仅对未持久化的对象调整判定逻辑,持久化后仍沿用原有按Id比较的规则,完全兼容现有基础设施要求:
using System.Runtime.CompilerServices; public class Allegato : BaseObject<Allegato, int> { public override int Id { get; set; } public virtual string NomeFile { get; set; } public override bool BaseObjectEquals(BaseObject<Allegato, int> other) { if (other == null) return false; // 两个对象均未持久化时按引用比较 if (Id == default && other.Id == default) { return ReferenceEquals(this, other); } // 已持久化对象沿用原有Id比较逻辑 return base.BaseObjectEquals(other); } public override int GetHashCode() { // 未持久化时返回引用哈希码,已持久化时返回Id的哈希码 return Id == default ? RuntimeHelpers.GetHashCode(this) : base.GetHashCode(); } }
该方案无需修改业务代码和映射配置,改动量最小,也不会影响现有NHibernate的运行逻辑。
方案2:调整集合映射为Bag类型
如果业务允许同一个Allegato对象多次关联到Corso,可以直接将集合的映射策略从默认的Set改为Bag,Bag对应运行时实现为List,不会做去重校验,自然不会触发相等性判定:
- 若使用Fluent NHibernate映射,在
Corso的映射配置中添加AsBag()声明即可:
HasMany(x => x.Allegati) .AsBag() .Cascade.AllDeleteOrphan(); // 沿用原有级联配置即可
- 若使用hbm.xml映射,将集合的
<set>标签替换为<bag>标签即可。
方案3:调整业务代码保存顺序
如果既不能修改实体定义也不能调整映射,可以调整业务代码的执行顺序,先保存父对象Corso,再逐个添加子对象并保存,确保每个Allegato添加到集合前已经拿到数据库生成的唯一Id:
using (myUow = myUowFactory.Create()) { var obj = new Corso(); // 填充Corso的其他属性 myUow.Corsi.Create(obj); myUow.SaveAll(); // 先保存父对象,此时关联集合为空 var files = SessionManagement.LeggiOggetto<SessionObject.File>(HttpContext, SessionManagement.ChiaveSessione.File); foreach (var file in files) { var allegato = new Allegato { NomeFile = file.Nome }; obj.AddAllegato(allegato); myUow.Allegati.Create(allegato); // 逐个保存子对象,生成唯一Id } myUow.SaveAll(); // 提交最终变更 }
内容的提问来源于stack exchange,提问作者gt.guybrush
相关产品推荐
相关产品推荐

