NHibernate - Запрос из коллекции типов значений (не-сущностей) для решения Select N + 1 - PullRequest
0 голосов
/ 11 сентября 2010

У меня есть объект, который представляет твит из Twitter, например:

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, который выбирает список твитов с дополнительным столбцом, который указывает, является ли конкретныйПользователь по его идентификатору проголосовал за каждый твит.Когда я голосую за твит, я сохраняю его в коллекции VoterIds выше.

Для этого я использую коллекцию типов значений, поскольку меня интересует только идентификатор пользователя Twitter, чтобы определить,пользователь уже проголосовал за определенный твит.Следовательно, почему это 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>();

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

Я пытался использовать Expressions.In:

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

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


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

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

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

1 Ответ

1 голос
/ 27 октября 2010

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

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

Вот пример, основанный на предположении, что ваш класс Tweet сопоставлен примерно так ...

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

...