2010-09-11 7 views
0

У меня есть объект, который представляет твит из Twitter, как так:NHibernate - Запрос из коллекции типов значений (не Entity) для решения Выберите 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; } 
} 

Я пытаюсь запустить запрос в NHibernate, который выбирает список твитов с дополнительным столбцом, который обозначает, проголосовал ли конкретный пользователь своим UserId за каждый твит. Когда я голосую за твит, он хранится в коллекции «VoterIds» выше.

Я использую набор значений типов для этого, так как я действительно заинтересован в Twitter UserId, чтобы определить, проголосовал ли пользователь за конкретный твит. Следовательно, почему что это ISet<long> вместо ISet<Vote>

Я пытаюсь использовать прогнозы, как так:

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>(); 

Я думал, что правильный метод должен был использовать Projections.Conditional, но я не уверен, как использовать Это. Может кто-то помочь мне заполнить бит //---- WHAT GOES HERE!!?? в приведенном выше коде.

Я попытался с помощью Expressions.In:

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

... но он дал мне 'Не удается использовать коллекции с InExpression' ошибки. Пожалуйста помоги!


Update: Я начинаю думать, что это не представляется возможным запрашивать коллекции типов значений на всех, и что я должен использовать полномасштабный объект следующим образом:

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

... будет ли это так?

ответ

1

Вы можете сделать это, но изменение модели домена, чтобы обойти ограничение NHibernate, болезненно для души. Можно запросить коллекции значений с помощью HQL, но ICriteria действительно удобна для построения запросов с логикой. Единственный способ, которым я знаю, как запрашивать коллекции значений с помощью ICriteria, - это настраиваемый SQL. Это также больно и связывает ваш код с вашей базой данных (!), Но для меня это меньшее из трех зол. Мое обоснование заключается в том, что ICriteria в конечном итоге разрешит такой запрос, и боль может быть реорганизована позже.

Хитрость заключается в использовании подзапроса в пользовательском SQL, чтобы можно было присоединиться к таблице коллекций. Использование псевдонима таблицы, который не будет наступать на псевдонимы NHibernate, также является хорошей идеей (в данном случае custom_sql_t_v). И обратите внимание на {alias} и? которые будут заменены NHibernate.

Вот пример, основанный на предположении, ваш класс Твитнуть сопоставлен что-то вроде этого ...

<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> 

Вот модифицированный запрос с использованием T-SQL (например, 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>(); 

окончательный SQL генерируется NHibernate (я использовал NHibernate 2.1.2.4000) выглядит следующим образом ...

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' 

Поверхность всего этого заключается в том, что выполнение LIKE в отношении коллекции строк возможно - то, что я не думаю, может быть сделано с помощью HQL.