Question

Je me demande s’il existe un moyen relativement facile d’étendre NHibernate pour soutenir le syndicat discriminé de F #. Pas simplement un seul IUserType ou ICompositeUserType, mais quelque chose de générique que je peux réutiliser indépendamment du contenu réel du DU.

Par exemple, supposons que je possède une propriété appelée RequestInfo, qui est une union définie comme suit:

type RequestInfo =  
    | Id of int
    | Name of string

Ceci est compilé dans une classe abstraite RequestInfo, avec des sous-classes concrètes Id et Name. Je peux obtenir toutes ces informations avec la réflexion F #. Dans ce cas, je pourrais le stocker dans la base de données avec "RequestInfo_Tag", "RequestInfo_Id", "RequestInfo_Name".

En tant que novice chez NHibernate, quel genre de problèmes vais-je rencontrer en essayant de suivre cette approche? Des cas plus complexes vont-ils être impossibles à traiter? Par exemple, qu'en est-il des syndicats discriminés imbriqués? Y a-t-il un moyen que je peux "céder"? la lecture du reste de l'union à un autre ICompositeUserType?

Plus important encore, cela va-t-il gâcher mes capacités d'interrogation? Cela signifie que je devrai connaître les noms des colonnes dans la base de données; Je ne pourrai pas faire Criteria.Eq (SomeDiscUnion) et tout régler?

Je ne cherche pas un code complet " fournissez " répondez-y, juste quelques conseils généraux si cela vaut même la peine d’être poursuivi (et quelques indications sur la manière de le faire), ou si je devais simplement repenser mon modèle.

Merci!

P.S. Ne soyez pas impoli, mais si votre réponse consiste à "utiliser C #", ce n'est pas très utile.

Était-ce utile?

La solution

Je n'ai pas eu le courage d'essayer d'utiliser NHibernate avec le système de types de F #, mais il peut être utile de regarder du point de vue de ce qui est réellement généré par le compilateur F #.

Si vous regardez votre Union discriminée dans son réflecteur, trois classes sont en réalité générées (et davantage si vous comptez les mandataires de débogage privés).

public abstract class RequestInfo : IStructuralEquatable, IComparable, IStructuralComparable

La première classe, RequestInfo, est abstraite et est réellement implémentée par les autres types de l'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);
    }

alors quand vous le faites:

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

r et s sont des instances de _Id et _Name, respectivement.

La réponse à votre question est donc probablement la réponse à l’une des questions suivantes:

  • Comment mapper une classe abstraite dans nhibernate?
  • Comment faire en sorte que NHibernate utilise une méthode usine?
  • Comment créer une carte Nhibernate pour des objets immuables?
  • Comment puis-je implémenter un type personnalisé dans NHibernate (vraisemblablement avec IUserType).

Malheureusement, je ne suis pas assez avisé pour vous donner une réponse cohérente à ces questions, mais je suis sûr que quelqu'un d'autre ici a proposé au moins l'une de ces trois solutions.

J'aimerais penser que vous pouvez utiliser les mêmes méthodes que celles utilisées pour les stratégies d'héritage, en utilisant, par exemple, une colonne discriminante, mais je crains que l'absence de constructeur par défaut ne rende cela problématique. Je suis donc enclin à penser que l’utilisation d’un type personnalisé est la solution.

Après quelques manipulations, voici un type d'utilisateur personnalisé (probablement buggy et / ou cassé):

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

Ce code pourrait sûrement être plus générique et ne devrait probablement pas appartenir à votre couche de domaine, mais j’ai pensé qu’il serait utile de tenter une implémentation de la version F # de IUserType.

Votre fichier de mappage ferait alors quelque chose comme:

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

Vous pouvez probablement vous en sortir sans colonne pour "Type". avec un léger ajustement au code personnalisé UserType.

Je ne sais pas comment ces types d'utilisateurs personnalisés fonctionnent avec des requêtes / critères, car je n'avais pas vraiment travaillé avec des types d'utilisateurs personnalisés auparavant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top