我没有足够的勇气尝试将 NHibernate 与 F# 的类型系统一起使用,但从 F# 编译器实际生成的内容的角度来看可能会有所帮助。
如果您查看反射器中的区分联合,实际上会生成三个类(如果算上私有调试代理,还会生成更多类)。
public abstract class RequestInfo : IStructuralEquatable, IComparable, IStructuralComparable
第一个类 RequestInfo 是抽象的,实际上是由联合中的其他类型实现的。
// Nested Types
[Serializable, DebuggerTypeProxy(typeof(Program.RequestInfo._Id@DebugTypeProxy)), DebuggerDisplay("{__DebugDisplay()}")]
public class _Id : Program.RequestInfo
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
public readonly int id1;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public _Id(int id1);
}
[Serializable, DebuggerTypeProxy(typeof(Program.RequestInfo._Name@DebugTypeProxy)), DebuggerDisplay("{__DebugDisplay()}")]
public class _Name : Program.RequestInfo
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
public readonly string name1;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public _Name(string name1);
}
所以当你这样做时:
let r=Id(5)
let s=Name("bob")
r 和 s 分别是 _Id 和 _Name 的实例。
因此,您的问题的答案很可能是以下问题之一的答案:
- 如何映射到 nhibernate 中的抽象类?
- 如何让 NHibernate 使用工厂方法?
- 如何创建映射 Nhibernate 到不可变对象?
- 如何在 NHibernate 中实现自定义类型(可能使用 IUserType)。
不幸的是,我不够精明,无法对其中任何一个给出连贯的答案,但我确信这里的其他人至少已经完成了这三个解决方案中的一个。
我想您可以使用与继承策略相同的方法,例如使用鉴别器列,但我担心缺少默认构造函数会造成问题。所以我倾向于认为使用自定义类型是解决方案。
经过一番摆弄,这是一个(可能有问题和/或损坏的)自定义用户类型:
type RequestInfo =
| Id of int
| Name of string
type RequestInfoUserType() as self =
interface IUserType with
member x.IsMutable = false
member x.ReturnedType = typeof<RequestInfo>
member x.SqlTypes = [| NHibernate.SqlTypes.SqlType(Data.DbType.String); NHibernate.SqlTypes.SqlType(Data.DbType.Int32); NHibernate.SqlTypes.SqlType(Data.DbType.String) |]
member x.DeepCopy(obj) = obj //Immutable objects shouldn't need a deep copy
member x.Replace(original,target,owner) = target // this might be ok
member x.Assemble(cached, owner) = (x :> IUserType).DeepCopy(cached)
member x.Disassemble(value) = (x :> IUserType).DeepCopy(value)
member x.NullSafeGet(rs, names, owner)=
// we'll use a column as a type discriminator, and assume the first mapped column is an int, and the second is a string.
let t,id,name = rs.GetString(0),rs.GetInt32(1),rs.GetString(2)
match t with
| "I" -> Id(id) :> System.Object
| "N" -> Name(name) :> System.Object
| _ -> null
member x.NullSafeSet(cmd, value, index)=
match value with
| :? RequestInfo ->
let record = value :?> RequestInfo
match record with
| Id(i) ->
cmd.Parameters.Item(0) <- "I"
cmd.Parameters.Item(1) <- i
| Name(n) ->
cmd.Parameters.Item(0) <- "N"
cmd.Parameters.Item(2) <- n
| _ -> raise (new ArgumentException("Unexpected type"))
member x.GetHashCode(obj) = obj.GetHashCode()
member x.Equals(a,b) =
if (Object.ReferenceEquals(a,b)) then
true
else
if (a=null && b=null) then
false
else
a.Equals(b)
end
这段代码肯定可以变得更通用,并且可能不应该在您的实际领域层中,但我认为尝试 IUserType 的 F# 实现会很有用。
然后您的映射文件将执行以下操作:
<property name="IdOrName" type="MyNamespace.RequestInfoUserType, MyAssembly" >
<column name="Type"/>
<column name="Id"/>
<column name="Name"/>
</property>
只需对自定义 UserType 代码稍作调整,您就可以在没有“类型”列的情况下摆脱困境。
我不知道这些自定义用户类型如何与查询/ICriteria 一起使用,因为我之前没有真正使用过自定义用户类型。