2009-03-03 5 views
5

Comment mapper une classe à d'autres instances de la même classe lorsque cette relation possède elle-même les propriétés?Mappages NHibernate lorsque les relations d'auto-jointure ont des propriétés supplémentaires

J'ai une classe appelée personne qui est mise en correspondance avec une personne de table

PersonID PersonName PersonAge 
---------------------------------- 
     1 Dave Dee    55 
     2 Dozy     52 
     3 Beaky    45 
     4 Mick     55 
     5 Tich     58 

Je veux un grand nombre à plusieurs entre personne et personne à l'aide d'une table de jointure appelé PersonPerson:

PersonPersonID PersonID RelatedPersonID RelationshipID 
-------------------------------------------------------- 
       1   1    5    1 
       2   3    4    2 
       3   2    1    3 

Je veux que les attributs suivants dans la table PersonPerson:

RelationshipID RelationshipName 
-------------------------------- 
      1 Colleague 
      2 Manager 
      3 Tutor 

This question et le lien-à post by Billy McCafferty explique que la relation PersonPerson doit être promue à partir d'un JOIN normal à une entité de son propre droit en raison des colonnes supplémentaires dans la table PersonPerson. Cependant, cela n'explique pas ce qu'il faut faire quand il s'agit d'une auto-participation. La différence étant que si je demande à toutes les personnes liées à Dave Dee (ID = 1), non seulement devrais-je obtenir Tich (ID = 5), mais je devrais obtenir aussi obtenir Dozy (ID = 2) car Dave Dee est également dans la colonne RelatedPersonID.

Ce que ma solution est jusqu'à présent, est d'avoir deux propriétés dans ma classe Person.

public virtual IList<PersonPerson> PersonPersonForward {get;set;} 
public virtual IList<PersonPerson> PersonPersonBack {get;set;} 

private List<PersonPerson> personPersonAll; 
public virtual List<PersonPerson> PersonPersonAll 
{ 
    get 
    { 
     personPersonAll = new List<PersonPerson>(PersonPersonForward); 
     personPersonAll.AddRange(PersonPersonBack); 
     return personPersonAll; 
    } 
} 

et ont les éléments suivants dans l'HBM:

<bag name="PersonPersonForward" table="PersonPerson" cascade="all"> 
     <key column="PersonID"/> 
     <one-to-many class="PersonPerson" /> 
</bag> 

<bag name="PersonPersonBack" table="PersonPerson" cascade="all"> 
     <key column="RelatedPersonID"/> 
     <one-to-many class="PersonPerson" /> 
</bag> 

Cela semble un peu maladroit et inélégante. NHibernate a habituellement des solutions élégantes à la plupart des problèmes quotidiens. Est-ce que ce qui précède est la façon sensée de le faire ou existe-t-il un meilleur moyen?

Répondre

2

Je pense que je ferais comme ça aussi, mais, je pense que c'est un peu «maladroit» de le modéliser comme ça. Je veux dire: vous avez une collection de personnes à laquelle une certaine personne est liée, mais vous avez aussi une «relation de retour».
Est-ce vraiment nécessaire? N'est-ce pas une option pour supprimer cette back-collection et à la place, spécifier une méthode sur le PersonRepository qui peut vous donner toutes les personnes qui ont une sorte de relation avec une personne donnée? Hmm, cela peut sembler un peu obscur, alors voici un peu de code (notez que par souci de brièveté, j'ai omis les modificateurs 'virtuels' etc ... (Je préfère aussi ne pas avoir ces modificateurs , donc dans 99% du temps, je précise « paresseux = false » à ma classe-mapping).

public class Person 
{ 
    public int Id {get; set;} 
    public string Name {get; set;} 

    public IList<PersonPerson> _relatedPersons; 

    public ReadOnlyCollection<PersonPerson> RelatedPersons 
    { 
     get 
     { 
      // The RelatedPersons property is mapped with NHibernate, but 
      // using its backed field _relatedPersons (can be done using the 
      // access attrib in the HBM. 
      // I prefer to expose the collection itself as a readonlycollection 
      // to the client, so that RelatedPersons have to be added through 
      // the AddRelatedPerson method (and removed via a RemoveRelatedPerson method). 

      return new List<PersonPerson) (_relatedPersons).AsReadOnly(); 
     } 
    } 

    public void AddRelatedPerson(Person p, RelationType relatesAs) 
    { 
     ... 
    } 

} 

Comme vous pouvez le voir, la classe Person a une seule collection à gauche, qui est une collection de PersonPerson Afin d'obtenir les Personnes qui ont des relations avec une Personne donnée, vous pouvez créer une méthode spécifique sur votre PersonRepository qui renvoie ces Personnes, au lieu de les avoir dans une collection de la classe Person. je pense cela améliorera également les performances.

public class NHPersonRepository : IPersonRepository 
{ 
    ... 

    public IList<Person> FindPersonsThatHaveARelationShipWithPerson(Person p) 
    { 
     ICriteria crit = _session.CreateCriteria <Person>(); 

     crit.AddAlias ("RelatedPersons", "r"); 

     crit.Add (Expression.Eq ("r.RelatedWithPerson", p)); 

     return crit.List(); 

    } 
} 

La 'référence arrière' n'est pas un membre de la classe Person; il doit être accessible via le référentiel.C'est aussi ce que dit Eric Evans dans son livre DDD: dans certains cas, il est préférable d'avoir une méthode spécialisée sur le référentiel qui peut vous donner accès aux objets associés, au lieu de les avoir (= les objets associés) à porter autour de l'objet lui-même.

Je n'ai pas testé le code, je l'ai juste tapé ici, donc je n'ai pas non plus vérifié l'erreur de syntaxe, etc ... mais je pense que cela devrait clarifier un peu comment je verrais ça.

+0

@Frederik Gheysels Bonne réponse, je vais essayer ça maintenant. Cela semble une solution évidente maintenant que vous l'avez dit! –

+0

@Frederik - J'aime l'idée de faire cela dans le dépôt, mais je ne sais toujours pas comment je récupérerais toutes les instances liées et les types de relations. –

+0

Je retournerais les objets Personne qui ont une relation avec la personne donnée. Bien sûr, ces objets Person ont leur collection 'PersonPerson' qui contient toutes les relations que cette personne a. –

2

Il me semble que vous avez essentiellement construit un modèle d'un directed graph, et les deux correspondances PersonPersonForward et PersonPersonBack représentent les bords sortants et entrants respectivement.

Cette directedness est renforcée par la sémantique de vos types de relations: tout est-un-collègue de est très probablement un symmetric relation, est-a-Manager-de et est-un-tuteur de sont presque définitivement asymétriques.

Je pense que dans ce cas le modèle de données essaie de vous dire que les deux collections de liens, bien que de type compatible, ne sont pas la même chose en contexte.

+0

Ce sont des tables légèrement artificielles, mais je prends votre point de vue. Je voudrais que les relations symétriques soient traitées de la même manière que les relations asymétriques. Cela ne veut-il pas simplement dire que les symétriques sont les mêmes dans chaque sens? –

+0

Oui, un bord non dirigé d'un graphe orienté serait représenté par une paire d'arêtes dirigées, une pointant dans chaque direction. –

+0

Si vous êtes vraiment après un graphique semi-orienté hybride, essayez de séparer les relations symétriques dans leur propre table avec une contrainte de vérification (left-id <= id-right) pour la normalisation, et peut-être une vue avec des mutateurs appropriés définis pour exprimer la symétrie relationnellement. –

Questions connexes