2009-10-26 13 views
15

Me pregunto si hay alguna manera relativamente fácil de extender NHibernate para que admita la unión discriminada de F #. No solo un solo IUserType o ICompositeUserType, sino algo genérico que puedo reutilizar independientemente del contenido real del DU.Sindicatos discriminados en NHibernate

Por ejemplo, supongamos que tengo una propiedad llamada requestInfo, que es una unión definida como:

type RequestInfo = 
    | Id of int 
    | Name of string 

Esto compila en una clase requestInfo abstracto, con las subclases concretas identificador y el nombre. Puedo obtener toda esta información muy bien con la reflexión F #. En este caso, podría almacenarlo en la base de datos con "RequestInfo_Tag", "RequestInfo_Id", "RequestInfo_Name".

Como soy un novato NHibernate, ¿qué tipo de problemas me voy a encontrar tratando de seguir este enfoque? ¿Serán imposibles de tratar los casos más complejos? Por ejemplo, ¿qué pasa con las uniones discriminadas anidadas? ¿Hay alguna manera de "transferir" la lectura del resto de la unión a otro ICompositeUserType?

Más importante aún, ¿esto arruinará mis capacidades de consulta? Es decir, tendré que saber los nombres de las columnas reales en el DB; No podré hacer Criteria.Eq (SomeDiscUnion) y tenerlo todo resuelto?

No estoy buscando una respuesta completa de "proporcionar código", solo algunos consejos generales si esto vale la pena ir después (y algunos consejos sobre cómo), o si simplemente debería replantear mi modelo.

Gracias!

P.S. No debe ser grosero, pero si su respuesta es "use C#", no es muy útil.

+1

Pregunta muy interesante. Soy bastante fluido tanto en NHibernate como en F #, abordaré esto cuando tenga algo de tiempo –

+0

Al menos en mi experiencia, tuve dificultades para usar NHibernate con F #, principalmente porque NHibernate requiere que los objetos tengan un constructor predeterminado (que es con frecuencia no es el caso para tipos de unión, tipos de registros y la mayoría de las clases), y se basa en la mutabilidad para asignar valores a las clases (lo cual es frecuentemente molesto de tratar en las definiciones de clase F #). La mayoría de las veces, obtengo la base de datos para jugar con F # lanzando mi propio mirco-ORM. – Juliet

+0

Sí, en nuestro último proyecto simplemente nos apegamos a los tipos de objetos antiguos simples para que puedan jugar muy bien con NHibernate. Esta vez me gustaría diversificarme un poco y probar algunos modelos más realistas. – MichaelGG

Respuesta

7

No he sido lo suficientemente valiente como para intentar usar NHibernate con el sistema de tipos de F #, pero podría ayudar mirar desde la perspectiva de lo que realmente genera el compilador de F #.

Si mira su Unión discriminada en reflector, en realidad hay tres clases generadas (y más si cuenta los proxies de depuración privados).

public abstract class RequestInfo : IStructuralEquatable, IComparable, IStructuralComparable 

La primera clase, RequestInfo, es abstracta, y en realidad es implementada por los otros tipos en la unión.

// Nested Types 
    [Serializable, DebuggerTypeProxy(typeof([email protected])), 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([email protected])), DebuggerDisplay("{__DebugDisplay()}")] 
    public class _Name : Program.RequestInfo 
    { 
     // Fields 
     [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode] 
     public readonly string name1; 

     // Methods 
     [CompilerGenerated, DebuggerNonUserCode] 
     public _Name(string name1); 
    } 

así que cuando lo hace:

let r=Id(5) 
let s=Name("bob") 

r y s son ejemplos de _ID y _NAME, respectivamente.

Así que la respuesta a su pregunta es probable que la respuesta a una de las siguientes preguntas:

  • ¿Cómo puedo asignar a una clase abstracta en nhibernate?
  • ¿Cómo puedo hacer que NHibernate use un método de fábrica?
  • ¿Cómo puedo crear un mapa de Nhibernate para objetos inmutables?
  • ¿Cómo implemento un tipo personalizado en NHibernate (presumiblemente con IUserType)?

Desafortunadamente, no soy lo suficientemente inteligente para dar una respuesta coherente a ninguno de ellos, pero estoy seguro de que alguien más ha hecho al menos una de estas tres soluciones.

Me gustaría pensar que puede utilizar los mismos métodos utilizados para las estrategias de herencia, utilizando, por ejemplo, una columna discriminatoria, pero me temo que la falta de un constructor predeterminado lo hace problemático. Entonces, me inclino a pensar que usar un tipo personalizado es la solución.

Después de algún tocar el violín, he aquí una (posiblemente con errores y o roto) Tipo de usuario personalizada:

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 

Este código de seguridad podría ser más genérico, y probablemente no debería estar en la capa de dominio real, pero pensé que sería útil apuñalar una implementación de F # de IUserType.

Su archivo de asignación sería entonces hacer algo como:

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

Es probable que pueda salir de allí sin una columna para el "Tipo", con una ligera inclinación al código UserType personalizado.

No sé cómo funcionan estos tipos de usuarios personalizados con consultas/ICriteria, ya que en realidad no he trabajado mucho con tipos de usuarios personalizados.

+0

Sí, mapear una sola instancia debería ser relativamente fácil. ¿Pero qué pasa cuando un DU contiene otro DU o algo así? Creo que necesito un tipo de usuario compuesto avanzado que sea recursivo para que este tipo de cosas se correlacione correctamente en todos los casos. También me preocupa cómo se muestra esto para hacer un criterio. – MichaelGG

+0

No estoy seguro de cómo manejaría eso, tampoco, pero sospecho que podría usar una construcción de coincidencia de patrones más profunda similar a mi ejemplo anterior. – JasonTrue