I want to retrieve the list of short User
versions with their Characters
collection (short version too). I use QueryOver with projections.
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>();
When I run this code it throws an exception: Could not find a setter for property ClassId in class UserDto
. If I remove the transformer then the result contains entries for one character per each user (User1-Character1, User1-Character2, ...). Each entry has correct nickname and character class id but wrong xp (user id instead) and characters collection (== null).
How can I get the correct data without using hql or doing seperate queries for characters?
update
I removed the transformer and commented out Projections.Property(() => userAlias.Characters)...
, now it returns the correct results but is there a way to transform them into projected dtos like AliasToBeanTransformer does and collapse the repeated user data for each character entries?
You are trying to project to two different object types ( UserDto
and CharacterDto
). I don't think it's supported. If you look at your code, you tell NHibernate that he should use UseDto
( Transformers.AliasToBean<UserDto>()
) but you don't tell it about CharacterDto
.
See for example http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/ :
You cannot populate collections (eg, if you had a class with ProductID and a collection of ProductReviews you could not do that in one step using AliasToBean)
What you can do:
Add a class with everything ( 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; }
}
and first you project to 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>()
then you GroupBy
and in the end you create your elements
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();
}
}
(note that I'm not using the functional syntax of LINQ because I want to use the let
keyword)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.