【问题标题】:Discriminated unions in NHibernateNHibernate 中的歧视性工会
【发布时间】:2009-10-26 07:06:58
【问题描述】:

我想知道是否有任何相对简单的方法可以扩展 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?

更重要的是,这会破坏我的查询能力吗?意思是,我必须知道数据库中的实际列名吗?我将无法执行 Criteria.Eq(SomeDiscUnion) 并解决所有问题?

我不是在寻找一个完整的“提供代码”答案,只是一些一般性的建议,如果这甚至值得去做(以及一些关于如何做的指示),或者我是否应该重新考虑我的模型。

谢谢!

附:不要粗鲁,但如果您的答案包含“使用 C#”,那么它不是很有帮助。

【问题讨论】:

  • 非常有趣的问题。我在 NHibernate 和 F# 方面都相当流利,有时间我会解决这个问题
  • 至少根据我的经验,我很难将 NHibernate 与 F# 一起使用,主要是因为 NHibernate 要求对象具有默认构造函数(联合类型、记录类型和大多数类通常不是这种情况),并且它依赖于可变性来为类分配值(在 F# 类定义中处理这通常很烦人)。通常情况下,我通过滚动自己的 mirco-ORM 来让数据库与 F# 一起使用。
  • 是的,在我们上一个项目中,我们只是坚持使用普通的旧对象类型,以便它们可以很好地与 NHibernate 配合使用。这次我想扩展一点,尝试一些更真实的模型。
  • 如果你没有得到很好的答案,我认为 NHibernate 的 F# API 将是一个从 Codeplex 开始的很棒的项目:)
  • 是的,是的:)。我不介意把事情整理出来,我只是不太了解 NHibernate 来选择正确的路径。

标签: nhibernate f# nhibernate-mapping


【解决方案1】:

我没有足够的勇气尝试将 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 一起使用,因为我之前没有真正使用过自定义用户类型。

【讨论】:

  • 是的,映射单个实例应该相对容易。但是当一个 DU 包含另一个 DU 或其他东西时呢?我认为我需要一些高级复合用户类型,它是递归的,以便在所有情况下都能正确映射。我也很担心这如何体现在做标准上。
  • 我也不确定如何处理,但我怀疑您可以使用类似于我上面示例的更深层次的模式匹配结构。
猜你喜欢
  • 2019-05-04
  • 2011-11-17
  • 2015-01-22
  • 1970-01-01
  • 2013-09-30
  • 2017-06-08
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
相关资源
最近更新 更多