3

我有简单的用户实体:

public class User
{
    public virtual int Id { get; set; }
    public virtual DateTime CreationDate { get; set; }
    public virtual DateTime ModifiedDate { get; set; }

    public virtual string Email { get; set; }
    public virtual string Name { get; set; }

    public virtual IList<Phone> Phones { get; set; }
}

public class Phone
{
    public virtual string CountryCode { get; set; }
    public virtual string Code { get; set; }
    public virtual string Number { get; set; }
    public virtual string Comment { get; set; }
}

我的映射定义如下:

public class UserMap : ClassMap<User>
{
    public UserMap ()
    {
        this.Table ("Users");

        this.Id (x => x.Id).CustomSqlType ("bigint").GeneratedBy.HiLo ("1000");
        this.Map (x => x.CreationDate);
        this.Map (x => x.ModifiedDate).Column ("LastUpdatedDate");
        this.Map (x => x.Email).Length (255).Not.Nullable ().Unique ();
        this.Map (x => x.Name).Column ("UserName").Length (255);

        this.HasMany (x => x.Phones).Inverse ();
    }
}

public class PhoneMap : ClassMap<Phone>
{
    public PhoneMap ()
    {
        this.Table ("Phones");

        this.Id ().GeneratedBy.Identity ();
        this.Map (x => x.CountryCode).Length (5);
        this.Map (x => x.Code).Length (10);
        this.Map (x => x.Number).Length (50).Not.Nullable ();
        this.Map (x => x.Comment).Length (255);
    }
}

此处的其他约定:

PrimaryKey.Name.Is (x => "Id"),
ForeignKey.EndsWith ("Id"),
DefaultAccess.Property (),
DefaultCascade.All ()

我需要选择前 100 名使用电话且名称以“A”开头的用户。但我需要在其中加载带有电话的用户对象。

所以我做这个查询:

var users =
(
    from user in session.Query<User> ()
    where
        user.Name.StartsWith ("a")
        &&
        user.Phones.Any ()
    select user
)
    .Fetch (x => x.Phones)
    .Take (100)
    .ToArray ();

而我只有 72 个用户。

为什么?好吧,因为 NHibernate 使用左外连接生成单个 TOP N 选择,并且 SQL 返回同一用户实体的多条记录,因为某些用户确实拥有不止一部手机。但这都是针对 TOP N 的——所以我得到了 100 条与手机连接的用户记录,但其中只有 72 条是唯一实体。

有正确的方法吗?

4

3 回答 3

4

您必须将查询拆分为子选择。其中内部子选择应该进行分页,外部应该进行获取:

var top100users =
(
    from user in session.Query<User>()
    where user.Name.StartsWith("a") &&
          user.Phones.Any()
    select user
)
.Take(100);

var users =
(
    from user in session.Query<User>()
    where top100users.Contains(user)
    select user
)
.Fetch (x => x.Phones)
.ToArray();

这将生成单个 sql 查询,其行为将如您所愿。

于 2012-05-29T07:02:17.320 回答
1

好吧,我想出的唯一可能的解决方法是首先从查询中删除 Fetch ,所以它变成了这样:

var users =
    (
        from user in session.Query<User> ()
        where
            user.Name.StartsWith (prefix)
            &&
            user.Phones.Any ()
        select user
    )
    .Take (100)
    .ToList ();

然后在该代码之后添加类似这样的内容,强制加载至少一个实体:

users.ForEach (x => x.Phones.Any ());

在映射中将批量大小设置为 100(或至少 50):

public class UserMap : ClassMap<User>
{
    public UserMap ()
    {
        this.Table ("Users");

        this.Id (x => x.Id).CustomSqlType ("bigint").GeneratedBy.HiLo ("1000");
        this.Map (x => x.CreationDate);
        this.Map (x => x.ModifiedDate).Column ("LastUpdatedDate");
        this.Map (x => x.Email).Length (255).Not.Nullable ().Unique ();
        this.Map (x => x.Name).Column ("UserName").Length (255);

        this.HasMany (x => x.Phones).Inverse ().BatchSize (50);
    }
}

或者通过约定(对于某些系统来说它可能不是那么优雅):

PrimaryKey.Name.Is (x => "Id"),
ForeignKey.EndsWith ("Id"),
DefaultAccess.Property (),
DefaultCascade.All (),
DynamicUpdate.AlwaysTrue (),
new CollectionConventionBuilder ().Always (x => x.BatchSize (50))

顺便说一句,在纯 SQL 中,使用“for xml”可以相当简单地解决任务:

select top 100
    u.Id,
    u.CreationDate,
    u.LastUpdatedDate,
    u.Email,
    u.UserName,
    (
        select
            p.CountryCode,
            p.Code,
            p.Number,
            p.Comment
        from
            dbo.Phones as p
        where
            p.UserId = u.Id
        for xml path ('Phone'), root ('Phones'), type
    ) as '*'
from
    dbo.Users as u
where
    u.UserName like @0
    and
    exists (select top 1 p.Id from dbo.Phones as p where p.UserId = u.Id)
for xml path ('User'), root ('Root'), type

我希望 NHibernate 可以在订购时从“for xml”查询中加载聚合根。

于 2012-05-25T07:13:27.110 回答
0

您需要使用子查询(用于分页)和转换器来获得不同的用户,我不确定这在 NHibernate Linq 提供程序中是否可行,所以使用 QueryOver:

var sub_query = QueryOver.Of<User>() 
    .Where (Restrictions.On<User>(x => x.Name).IsLike("a%")) 
    .JoinQueryOver(x => x.Phones, JoinType.InnerJoin) 
    .Take (100)
    .Select(x => x.Id);

var users = session.QueryOver<User>() 
    .WithSubquery.WhereProperty (x => x.Id).In (sub_query) 
    .Fetch (x => x.Phones).Eager
    .TransformUsing (Transformers.DistinctRootEntity) 
    .List ();
于 2012-05-23T16:19:53.917 回答