NHibernate中的歧视联盟

Mic*_*lGG 15 nhibernate f# nhibernate-mapping

我想知道是否有任何相对简单的方法来扩展NHibernate以支持F#的歧视联盟.不仅仅是单个IUserType或ICompositeUserType,而是一些通用的东西,无论DU的实际内容如何,​​我都可以重复使用.

例如,假设我有一个名为RequestInfo的属性,它是一个定义为的联合:

type RequestInfo =  
    | Id of int
    | Name of string
Run Code Online (Sandbox Code Playgroud)

这将编译为一个抽象的RequestInfo类,具有具体的子类Id和Name.我可以通过F#反射得到所有这些信息.在这种情况下,我可以使用"RequestInfo_Tag","RequestInfo_Id","RequestInfo_Name"将其存储在数据库中.

因为我是NHibernate的新手,我会遇到什么样的问题试图遵循这种方法?更复杂的案件是不可能处理的?例如,嵌套的歧视联盟呢?有没有办法可以将其他联盟的读取"移交"到另一个ICompositeUserType?

更重要的是,这会搞砸我的查询功能吗?意思是,我是否必须知道DB中的实际列名; 我将无法执行Criteria.Eq(SomeDiscUnion)并将其全部整理出来?

我不是在寻找一个完整的"提供代码"的答案,只是一些一般的建议,如果这是值得追求的(以及如何),或者我应该重新考虑我的模型.

谢谢!

PS不要太粗鲁,但如果你的答案是"使用C#",那就不是很有帮助了.

Jas*_*rue 7

我没有勇敢尝试将NHibernate与F#的类型系统一起使用,但从F#编译器实际生成的角度来看可能会有所帮助.

如果你在反射器中查看Discriminated Union,实际上会生成三个类(如果计算私有调试代理,则会有更多类).

public abstract class RequestInfo : IStructuralEquatable, IComparable, IStructuralComparable
Run Code Online (Sandbox Code Playgroud)

第一个类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);
    }
Run Code Online (Sandbox Code Playgroud)

所以当你这样做时:

 let r=Id(5)
 let s=Name("bob")
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这个代码肯定会更通用,并且可能不应该在你的实际域层中,但我认为对IUserType的F#实现进行一次尝试会很有用.

您的映射文件将执行以下操作:

<property name="IdOrName" type="MyNamespace.RequestInfoUserType, MyAssembly"  >
  <column name="Type"/>
  <column name="Id"/>
  <column name="Name"/>
</property>
Run Code Online (Sandbox Code Playgroud)

您可能会在没有"Type"列的情况下离开,只需稍微调整自定义UserType代码即可.

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