Дискриминационные союзы в NHibernate - PullRequest
15 голосов
/ 26 октября 2009

Мне интересно, есть ли относительно простой способ расширить 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) и разобраться со всем этим?

Я не ищу полного ответа «предоставить код», просто какой-то общий совет, если к этому стоит даже обратиться (и некоторые указания о том, как), или если мне следует просто переосмыслить свою модель.

Спасибо!

P.S. Не хамить, но если ваш ответ состоит из «используйте C #», это не очень полезно.

1 Ответ

7 голосов
/ 28 октября 2009

Я недостаточно смел, чтобы попытаться использовать 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

Этот код, безусловно, можно сделать более общим, и, вероятно, его не должно быть в вашем фактическом доменном слое, но я подумал, что было бы полезно попробовать FU-реализацию IUserType.

Ваш файл сопоставления будет делать что-то вроде:

<property name="IdOrName" type="MyNamespace.RequestInfoUserType, MyAssembly"  >
  <column name="Type"/>
  <column name="Id"/>
  <column name="Name"/>
</property>

Вероятно, вы можете обойтись без столбца для «Типа» с небольшим изменением пользовательского кода UserType.

Я не знаю, как эти пользовательские типы пользователей работают с запросами / критериями, так как раньше я не очень много работал с пользовательскими типами.

...