我想知道是否有任何相对简单的方法来扩展NHibernate以支持F#的歧视联盟。不仅仅是单个IUserType或ICompositeUserType,而是一些通用的东西,无论DU的实际内容如何,我都可以重复使用。
例如,假设我有一个名为RequestInfo的属性,它是一个定义为:
的联合type RequestInfo =
| Id of int
| Name of string
这将编译成一个抽象的RequestInfo类,具有具体的子类Id和Name。我可以通过F#反射得到所有这些信息。在这种情况下,我可以使用“RequestInfo_Tag”,“RequestInfo_Id”,“RequestInfo_Name”将其存储在数据库中。
由于我是NHibernate的新手,我会遇到什么样的问题试图遵循这种方法?更复杂的案件是不可能处理的?例如,嵌套的歧视联盟呢?有没有办法可以将联盟其余部分的读取“移交”到另一个ICompositeUserType?
更重要的是,这会破坏我的查询功能吗?意思是,我是否必须知道DB中的实际列名;我将无法执行Criteria.Eq(SomeDiscUnion)并将其全部整理出来?
我不是在寻找一个完整的“提供代码”的答案,只是一些一般的建议,如果这是值得追求的(以及如何),或者我应该重新考虑我的模型。
谢谢!
P.S。不要太粗鲁,但如果你的回答是“使用C#”,那就不是很有帮助了。
答案 0 :(得分:7)
我没有勇敢尝试将NHibernate与F#的类型系统一起使用,但从F#编译器实际生成的内容来看可能会有所帮助。
如果你在反射器中查看Discriminated Union,实际上会生成三个类(如果计算私有调试代理,则会有更多类)。
public abstract class RequestInfo : IStructuralEquatable, IComparable, IStructuralComparable
第一个类RequestInfo是抽象的,实际上是由union中的其他类型实现的。
// 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的实例。
因此,您的问题的答案可能是以下问题之一的答案:
不幸的是,我不够精明,不能给你一个连贯的答案,但我相信其他人至少完成了这三个解决方案中的一个。
我想你可以使用与继承策略相同的方法,例如使用一个鉴别器列,但我担心缺少默认构造函数会导致这个问题。所以我倾向于认为使用自定义类型是解决方案。
经过一番摆弄后,这里有一个(可能是错误的或破碎的)自定义用户类型:
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>
你可能会在没有“Type”列的情况下离开,只需稍微调整一下自定义UserType代码。
我不知道这些自定义用户类型如何使用查询/ ICriteria,因为我之前并没有真正使用过自定义用户类型。