【问题标题】:Join collection in QueryOver with projections使用投影加入 QueryOver 中的集合
【发布时间】:2015-04-30 08:39:57
【问题描述】:

我想用他们的Characters 集合检索简短的User 版本列表(也是简短版本)。我将 QueryOver 与投影一起使用。

public class User
{
        public virtual int Id { get; set; }
        public virtual string Nickname { get; set; }
        public virtual ISet<Character> Characters { get; set; }
        // and 30 other properties
}

public class Character
{
        public virtual int Id { get; set; }
        public virtual int XP { get; set; }
        public virtual int UserId { get; set; }
        public virtual int ClassId { get; set; }
        // and 30 other properties
}

public class UserDto // short version
{
        public virtual string Nickname { get; set; }
        public virtual ISet<CharacterDto> Characters { get; set; }
}


public class CharacterDto // short version
{
        public virtual int XP { get; set; }
        public virtual int ClassId { get; set; }
}

    User userAlias = null;
    UserDto userDto = null;
    Character characterAlias = null;
    CharacterDto characterDto = null;

    var result = session.QueryOver<User>(() => userAlias)
            .Where(Restrictions.Like(
                Projections.SqlFunction("lower", NHibernateUtil.String, Projections.Property<User>(x => x.Nickname)),
                nickname.ToLowerInvariant(), MatchMode.Start))
            .JoinAlias(x => x.Characters, () => characterAlias, JoinType.LeftOuterJoin)
             .Select(
                 Projections.Property(() => userAlias.Nickname).WithAlias(() => userDto.Nickname),
                 Projections.Property(() => userAlias.Characters).WithAlias(() => userDto.Characters),
                 Projections.Property(() => characterAlias.XP).WithAlias(() => characterDto.XP),
                 Projections.Property(() => characterAlias.ClassId).WithAlias(() => characterDto.ClassId)
                )
            .TransformUsing(Transformers.AliasToBean<UserDto>())
             .Take(50)
             .List<UserDto>();

当我运行此代码时,它会引发异常:Could not find a setter for property ClassId in class UserDto。如果我删除转换器,则结果包含每个用户一个字符的条目(User1-Character1,User1-Character2,...)。每个条目都有正确的昵称和字符类 id,但错误的 xp(而不是用户 id)和字符集合(== null)。

如何在不使用 hql 或单独查询字符的情况下获取正确的数据?

更新

我删除了转换器并注释掉了Projections.Property(() =&gt; userAlias.Characters)...,现在它返回了正确的结果,但是有没有办法像 AliasToBeanTransformer 那样将它们转换为投影的 dto 并折叠每个字符条目的重复用户数据?

【问题讨论】:

  • 您正在尝试投影到两种不同的对象类型(UserDtoCharacterDto)。我不认为它被支持。如果你查看你的代码,你告诉 NHibernate 他应该使用UseDto (Transformers.AliasToBean&lt;UserDto&gt;()),但你没有告诉它CharacterDto
  • 好的,那么正确的方法是什么?另请参阅有关它不返回正确的 xp 值但使用 user_ids 代替的部分。
  • 通常这个Projections.SqlFunction("lower", 应该没用,因为SQL Server中的列默认是不区分大小写的。
  • @xanatos 我使用 postgresql

标签: c# nhibernate outer-join queryover nhibernate-projections


【解决方案1】:

您正在尝试投影到两种不同的对象类型(UserDtoCharacterDto)。我不认为它被支持。如果你查看你的代码,你告诉 NHibernate 他应该使用UseDto (Transformers.AliasToBean&lt;UserDto&gt;()),但你没有告诉它CharacterDto

例如见http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/

您无法填充集合(例如,如果您有一个具有 ProductID 的类和一个 ProductReviews 集合,则您无法使用 AliasToBean 一步完成)

你可以做什么:

添加一个包含所有内容的类 (UserCharacterDto)

public class UserCharacterDto // short version
{
    public virtual int Id { get; set; }
    public virtual string Nickname { get; set; }
    public virtual int? XP { get; set; }
    public virtual int? ClassId { get; set; }
}

public class UserDto // short version
{
    public virtual string Nickname { get; set; }
    // Changed from ISet: you don't have a key!
    public virtual IList<CharacterDto> Characters { get; set; }
}

public class CharacterDto // short version
{
    public virtual int XP { get; set; }
    public virtual int ClassId { get; set; }
}

首先你投射到UserCharacterDto

UserCharacterDto uc = null;

IList<UserCharacterDto> result = ...

    .Select(
        Projections.Property(() => userAlias.Nickname).WithAlias(() => uc.Nickname),
        Projections.Property(() => characterAlias.XP).WithAlias(() => uc.XP),
        Projections.Property(() => characterAlias.ClassId).WithAlias(() => uc.ClassId)
    )
    .TransformUsing(Transformers.AliasToBean<UserDto>())
    .Take(50)
    .List<UserCharacterDto>()

然后你 GroupBy 最后你创建你的元素

IList<UserDto> result2 = (from x in result
                          // Here you group by User
                          group x by x.Id into y
                          let first = y.First()
                          // Here you build the "definitive" UserDto
                          select new UserDto {
                              Nickname = first.Nickname,
                              // and here you build the "definitive" CharacterDto
                              // note the handling of empty objects!
                              Characters = new List<CharacterDto>(y.Select(z => z.XP != null && z.ClassId != null ? new CharacterDto { XP = z.XP.Value, ClassId = z.ClassId.Value } : null))
                          }
                         ).ToList();

// remove empty CharacterDto from left join
foreach (UserDto user in result2) {
    if (user.Characters.Count == 1 && user.Characters[0] == null) {
        user.Characters.Clear();
    }
}

(请注意,我没有使用 LINQ 的函数式语法,因为我想使用 let 关键字)

【讨论】:

  • ClassId 和 XP 不能为空。当没有字符时,db 为这些字段返回 null 并且 AliasToBeanTransformer 会抛出异常,是吗?我该如何手动处理?我可以让这些字段仅对短 dto 版本为空吗?
  • @Vlad 不,我做了一些更改,现在应该可以了
  • 谢谢,有趣的是我刚刚检查过(只有 nh 没有分组部分),没有 int? 的先前版本在没有字符时可以正常工作
  • .Take(50) 返回 50 个组合结果(包含重复的用户),但不是 50 个用户。是否可以限制返回的用户数量?
  • @Vlad 我回家的时候也在想同样的事情......我希望你不会注意到/不会在乎 :-) 不,这非常非常困难/几乎不可能解决这个问题。此时,您通常开始直接编写 SQL 和/或编写 SQL 视图并使用 ROW_NUMBER()。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-02
  • 2017-12-04
  • 1970-01-01
  • 1970-01-01
  • 2018-01-06
  • 1970-01-01
相关资源
最近更新 更多