NHibernate-从值类型集合(非实体)查询以解决选择N+1问题

NHibernate-从值类型集合(非实体)查询以解决选择N+1问题,nhibernate,nhibernate-projections,Nhibernate,Nhibernate Projections,我有一个实体表示来自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<l

我有一个实体表示来自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; }
}
…但它给了我一个“无法使用无表达式的集合”错误。请帮忙

更新:我开始认为根本不可能查询值类型的集合,我应该使用这样一个完整的实体:

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

…会是这样吗?

您可以这样做,但是修改域模型以绕过NHibernate的限制对灵魂来说是痛苦的。使用HQL查询值集合是可能的,但是ICriteria对于使用逻辑构造查询非常方便。我知道如何使用ICriteria查询值集合的唯一方法是使用自定义SQL。这也很痛苦,而且会将代码绑定到数据库!,但对我来说,这是三害中较小的一害。我的理由是,ICriteria最终将允许这种查询,而痛苦可以在以后重构

诀窍是在自定义SQL中使用子查询,以便可以连接到集合表。在本例中,使用不涉及NHibernate别名的表别名也是一个好主意。注意{alias}和?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>
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无法做到这一点

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