2010-09-11 12 views
0

j'ai une entité qui représente un Tweet de Twitter comme ceci:NHibernate - Interrogation à partir d'une collection de types de valeur (non-entité) pour résoudre Sélectionnez N + 1

public class Tweet 
{ 
    public virtual long Id { get; set; } 
    public virtual string Username { get; set; } 
    public virtual string Message { get; set; } 

    // other properties (snip)... 

    public virtual ISet<long> VoterIds { get; protected set; } 
} 

Je suis en train d'exécuter un requête dans NHibernate qui sélectionne une liste de tweets avec une colonne supplémentaire qui indique si un utilisateur particulier par son UserId a voté pour chaque Tweet. Lorsque je vote pour un Tweet, il est stocké dans la collection 'VoterIds' ci-dessus. J'utilise une collection de types de valeur pour cela, puisque je ne m'intéresse vraiment à l'UserId Twitter pour déterminer si un utilisateur a déjà voté pour un tweet particulier. Par conséquent pourquoi il est un ISet<long> au lieu de ISet<Vote>

Je suis en train d'utiliser les projections comme ceci:

long userId = 123; 

IList<TweetReport> tweets = Session.CreateCriteria<Tweet>() 
    .SetProjection(Projections.ProjectionList() 
     .Add(Projections.Id(), "Id") 
     .Add(Projections.Property("Username"), "Username") 
     .Add(Projections.Property("Message"), "Message") 
     .Add(Projections.Conditional(//---- WHAT GOES HERE!!?? 
    .SetResultTransformer(Transformers.AliasToBean<TweetReport>()) 
    .List<TweetReport>(); 

Je pensais que la bonne méthode était d'utiliser Projections.Conditional, mais je ne suis pas sûr de savoir comment utiliser il. Quelqu'un peut-il m'aider à remplir le bit //---- WHAT GOES HERE!!?? dans le code ci-dessus.

J'ai essayé d'utiliser Expressions.In:

.Add(Projections.Conditional(Expressions.In("VoterIds", new object[] { userId }), 
    Projections.Constant(true), Projections.Constant(false))) 

... mais il m'a donné une 'Ne peut pas utiliser les collections avec InExpression' erreur. S'il vous plaît aider!


Mise à jour: Je commence à penser qu'il est impossible d'interroger des collections de types de valeur du tout, et que je devrais utiliser une entité à part entière comme ceci:

public virtual ISet<Vote> Votes { get; protected set; } 

... serait-ce le cas?

Répondre

1

Vous pouvez le faire, mais modifier le modèle de domaine pour contourner une limitation de NHibernate est douloureux pour l'âme. Il est possible d'interroger des collections de valeurs avec HQL, mais ICriteria est vraiment pratique pour construire des requêtes avec la logique. La seule façon dont je sais comment interroger les collections de valeurs à l'aide d'ICriteria est le SQL personnalisé. C'est douloureux aussi, et lie votre code à votre base de données (!), Mais pour moi, c'est le moindre des trois maux. Ma raison est que ICriteria permettra finalement ce genre de requête et la douleur peut être refactored plus tard.

L'astuce consiste à utiliser une sous-requête dans le SQL personnalisé afin qu'une jointure à la table de collection soit possible. L'utilisation d'un alias de table qui ne marchera pas sur les alias NHibernate est également une bonne idée (dans ce cas, custom_sql_t_v). Et notez le {alias} et? des espaces réservés que NHibernate échangera.

Voici un exemple basé sur l'hypothèse de votre classe Tweet est mappée quelque chose comme ça ...

<class name="Tweet" table="Tweet"> 
    <id name="Id" unsaved-value="0"> 
     <generator class="identity"/> 
    </id> 
    <version name="Version" unsaved-value="0"/> 
    <property name="UserName"/> 
    <property name="Message"/> 
    <set name="Votes" table="Tweet_Votes"> 
     <key column="Tweet"/> 
     <element type="Int64" column="Vote"/> 
    </set> 
</class> 

est ici la requête modifiée en utilisant T-SQL (par exemple Microsoft SQL Server) ...

IList<TweetReport> tweets = Session.CreateCriteria<Tweet>() 
    .SetProjection(Projections.ProjectionList() 
     .Add(Projections.Id(), "Id") 
     .Add(Projections.Property("UserName"), "UserName") 
     .Add(Projections.Property("Message"), "Message") 
     .Add(Projections.Conditional(
      Expression.Sql(
       "EXISTS (SELECT 1 FROM [Tweet_Votes] custom_sql_t_v WHERE custom_sql_t_v.[Tweet] = {alias}.[Id] AND custom_sql_t_v.[Vote] = ?)", 
       userId, 
       NHibernateUtil.Int64), 
      Projections.Constant(true), 
      Projections.Constant(false)), "DidVote")) 
    .SetResultTransformer(Transformers.AliasToBean<TweetReport>()) 
    .List<TweetReport>(); 

Le SQL final généré par NHibernate (je NHibernate 2.1.2.4000) ressemble à ceci ...

exec sp_executesql N'SELECT this_.Id as y0_, this_.UserName as y1_, this_.Message as y2_, (case when EXISTS (SELECT 1 FROM [Tweet_Votes] custom_sql_t_v WHERE custom_sql_t_v.[Tweet] = this_.[Id] AND custom_sql_t_v.[Vote] = @p0) then @p1 else @p2 end) as y3_ FROM Tweet this_',N'@p0 bigint,@p1 char(1),@p2 char(1)',@p0=123,@p1='Y',@p2='N' 

L'avantage de tout cela est que faire un LIKE contre une collection de chaînes est possible - quelque chose que je ne pense pas pouvoir faire avec HQL.