如何使用FluentNHibernate将电子邮件地址集合映射为ICollection<EmailAddress>而非ICollection<string>
兄弟,我太懂你为了避免邮箱大小写踩坑写这个EmailAddress类的痛苦了!之前我在FluentNHibernate映射自定义值对象集合的时候也栽过一模一样的跟头,咱们来一步步解决这个映射异常的问题。
先说说你出错的根源
你在映射里用了.CollectionType<EmailAddressType>(),这是找错了地方!EmailAddressType是用来把单个数据库字符串列转换成EmailAddress对象的单个值类型映射,而不是整个集合的类型。NHibernate看到你这么配置,完全搞不懂怎么把集合里的字符串元素转成EmailAddress,自然就抛出“无法确定类型”的异常了。
正确的映射写法
咱们把集合映射的配置改一下,核心是告诉NHibernate:集合里的每个EmailAddress元素,要通过EmailAddressType来和数据库的EmailAddress字符串列做转换。修正后的PersonInfoMap代码如下:
public class PersonInfoMap : ClassMap<PersonInfo> { public PersonInfoMap() { Schema("dbo"); Table("Person"); Id(person => person.Id); ReadOnly(); HasMany(person => person.Emails) .Schema("dbo") .Table("PersonEmailView") .KeyColumn("PersonId") .Access.CamelCase(Prefix.Underscore) .AsSet() // 关键修改:在Element中指定元素的自定义类型 .Element("EmailAddress", element => element.Type<EmailAddressType>()) // 因为是从视图读取,加上只读配置更稳妥,避免NHibernate尝试写操作 .ReadOnly(); } }
还有个容易忽略的大坑!你的EmailAddress类有个隐患
我注意到你EmailAddress的GetHashCode()方法是直接用value.GetHashCode(),这会导致严重问题:比如Foo@Bar.com和foo@bar.com的哈希码是不同的,但你的Equals方法认为它们是相等的。而你用了.AsSet()(集合是HashSet),HashSet依赖哈希码的一致性,这会导致同一个邮箱的大小写不同版本被当成不同元素存入集合,完全违背了你的初衷!
赶紧把GetHashCode()修正成忽略大小写的版本:
public override int GetHashCode() => StringComparer.InvariantCultureIgnoreCase.GetHashCode(value);
最后验证一下
现在重新初始化NHibernate会话,应该就能正常加载PersonInfo的Emails集合了,每个元素都是EmailAddress对象,而且比较的时候会自动忽略大小写,再也不用手动记着加StringComparison了!
内容来源于stack exchange

