NHibernate中的歧视联盟

时间:2009-10-26 07:06:58

标签: nhibernate f# nhibernate-mapping

我想知道是否有任何相对简单的方法来扩展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#”,那就不是很有帮助了。

1 个答案:

答案 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的实例。

因此,您的问题的答案可能是以下问题之一的答案:

  • 如何映射到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>

你可能会在没有“Type”列的情况下离开,只需稍微调整一下自定义UserType代码。

我不知道这些自定义用户类型如何使用查询/ ICriteria,因为我之前并没有真正使用过自定义用户类型。

相关问题