[nhusers] Error using projections and a result collection

256 views
Skip to first unread message

Jim Geurts

unread,
May 7, 2010, 12:43:04 PM5/7/10
to nhusers
Hi all,

I'm trying to use projections to only return a few properties of my
domain object from the db. The code I'm using looks like:

var tenants = Session.QueryOver<Tenant>()
.Select(x => x.Id, x => x.FirstName, x => x.LastName)
.List<Tenant>();

The generated sql is spot on, but unfortunately, things are not
working when NHibernate tries to map the result to an
IEnumerable<Tenant>. It throws an exception similar to:

The value "System.Object[]" is not of type "MyDomain.Tenant" and
cannot be used in this generic collection.
Parameter name: value
System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value,
Type targetType) +122

System.Collections.Generic.List`1.System.Collections.IList.Add(Object
item) +150
NHibernate.Util.ArrayHelper.AddAll(IList to, IList from) in d:
\Builds\NHibernate\nhibernate\src\NHibernate\Util\ArrayHelper.cs:201
NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList
results) in d:\Builds\NHibernate\nhibernate\src\NHibernate\Impl
\SessionImpl.cs:1994

I've tried to set a transformer
(using .UnderlyingCriteria.SetResultTransformer(Transformers.AliasToBean(typeof(Tenant)))),
but it did not seem to help. This is using NHibernate 3.0.0.1001

Any ideas what is going on and how to fix it?

--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.

Richard Brown (gmail)

unread,
May 8, 2010, 4:44:07 AM5/8/10
to nhu...@googlegroups.com
Hi Jim,

QueryOver returns an object[] in this case (like ICriteria which it is built
on). I think your options are to use a ResultTransformer (which looking at
it is missing from the IQueryOver interface, but can be added to the
UnderlyingCriteria), or use an extension method from the LINQ namespace:

var tenants =
Session.QueryOver<Tenant>()
.Select(
x => x.Id,
x => x.FirstName,
x => x.LastName)
.List<object[]>()
.Select(properties => new Tenant() {
Id = (int)properties[0],
FirstName = (string)properties[1],
LastName = (string)properties[2],
});


Richard

--------------------------------------------------
From: "Jim Geurts" <jge...@gmail.com>
Sent: Friday, May 07, 2010 5:43 PM
To: "nhusers" <nhu...@googlegroups.com>
Subject: [nhusers] Error using projections and a result collection

Jim Geurts

unread,
May 8, 2010, 5:20:14 PM5/8/10
to nhusers
Thanks Richard for your response. If the NH dev team recommendation
is to use linq to cast the object array to the intended object type, I
feel this is a unacceptable and sloppy solution. Having the API
return an object array, when it *knows* the type of object makes me
feel like I'm using an API from 2003. Then having to box each
property and map them manually is tedious and error prone. I guess I
fail to see why queries with the QueryOver api and projections
wouldn't use the same method that NH uses to populate full objects.
If this is the intended behavior, then I consider this a bug, even if
it is a non-functional bug.

Also, I did try to use a ResultTransformer, but was unsuccessful:

Session.QueryOver<Tenant>()
.Select(x => x.Id, x => x.FirstName, x
=> x.LastName)
.UnderlyingCriteria.SetResultTransformer(Transformers.AliasToBean(typeof(Tenant)))
.List<Tenant>()

Is there a better way of doing this so that it works?

Thanks,

Jim



On May 8, 3:44 am, "Richard Brown \(gmail\)" <fluke...@googlemail.com>
wrote:
> Hi Jim,
>
> QueryOver returns an object[] in this case (like ICriteria which it is built
> on).  I think your options are to use a ResultTransformer (which looking at
> it is missing from the IQueryOver interface, but can be added to the
> UnderlyingCriteria), or use an extension method from the LINQ namespace:
>
> var tenants =
>     Session.QueryOver<Tenant>()
>         .Select(
>             x => x.Id,
>             x => x.FirstName,
>             x => x.LastName)
>         .List<object[]>()
>         .Select(properties => new Tenant() {
>             Id = (int)properties[0],
>             FirstName = (string)properties[1],
>             LastName = (string)properties[2],
>             });
>
> Richard
>
> --------------------------------------------------
> From: "Jim Geurts" <jgeu...@gmail.com>
> For more options, visit this group athttp://groups.google.com/group/nhusers?hl=en.

Richard Brown (gmail)

unread,
May 9, 2010, 6:02:34 AM5/9/10
to nhu...@googlegroups.com
Hi Jim,

> If the NH dev team recommendation
> is to use linq to cast the object array to the intended object type ...

That was a suggestion. If you want the mapped type, the recommendation
would be to retrieve it directly without projections:

Session.QueryOver<Tenant>()
...
.List();


> Having the API return an object array, when it *knows* the type of object
> ...
> fail to see why queries with the QueryOver api and projections
> wouldn't use the same method that NH uses to populate full objects.

The API can't know which object you are wanting as soon as you use
projections. The API allows arbitrary projections, so all bets are off on
the return type.

Incidentally, while there's no restriction to prevent you trying to manually
create partial Tenant objects, projections/result-transformers are more
typically use to create arbitrary non-mapped types (e.g., DTOs), not mapped
types.

If you simply return the complete objects directly (without projections)
then the query will "use the same method that NH uses to populate full
objects."


> Also, I did try to use a ResultTransformer, but was unsuccessful:

You need to alias each of the projections. I gave it a try with the
QueryOver API, and there isn't an easy way of doing this yet. I'll have a
look at making it easier to alias so you can write:

Tenant tenantAlias = null;
Session.QueryOver<Tenant>()
.Select(list => list
.Select(t => t.FirstName).WithAlias(() => tenantAlias.FirstName)
.Select(t => t.LastName).WithAlias(() => tenantAlias.LastName))
.SetResultTransformer(Transformers.AliasToBean<Tenant>())
.List<Tenant>();


For now, your best bet is probably to write your own IResultTransformer, or
simply return the full objects.

I hope that helps.

Richard

Jim Geurts

unread,
May 9, 2010, 5:20:43 PM5/9/10
to nhusers
Thanks for your help with this Richard. I look forward to see what
comes in the future for easier aliases. In the mean time, I'll look
into using a DTO and custom transformer that Ayende mentioned here:
http://ayende.com/Blog/archive/2007/08/26/Partial-Object-Queries-With-NHibernate.aspx

Thanks again,

Jim

On May 9, 5:02 am, "Richard Brown \(gmail\)" <fluke...@googlemail.com>
wrote:

Jim Geurts

unread,
May 9, 2010, 6:19:24 PM5/9/10
to nhusers
To all interested, I wrote a reusable/generic transformer that lets me
write type-safe code like:

Session.QueryOver<Tenant>()
.Select(x => x.Id, x => x.FirstName, x
=> x.LastName)
.UnderlyingCriteria.SetResultTransformer(new
PartialObjectTransformer<Tenant>(x => x.Id, x => x.FirstName, x =>
x.LastName))
.List<Tenant>()


The code for the transformer is as follows:


public class PartialObjectTransformer<T> : IResultTransformer
{
private readonly List<PropertyInfo> _properties;

public PartialObjectTransformer(params Expression<Func<T, object>>[]
properties)
{
_properties = new List<PropertyInfo>();
foreach (var property in properties)
{
Expression body = property;
if (body is LambdaExpression)
{
body = ((LambdaExpression)body).Body;
}
switch (body.NodeType)
{
case ExpressionType.Convert:
_properties.Add((PropertyInfo) ((MemberExpression)
((UnaryExpression) body).Operand).Member);
break;
case ExpressionType.MemberAccess:
_properties.Add((PropertyInfo) ((MemberExpression)
body).Member);
break;
default:
throw new InvalidOperationException();
}
}
}

public object TransformTuple(object[] tuple, string[] aliases)
{
var instance = Activator.CreateInstance(typeof(T));
for (var i = 0; i < tuple.Length; i++)
{
_properties[i].SetValue(instance, tuple[i], null);
}
return instance;
}

public IList TransformList(IList collection)
{
return collection;
}
}


Disclaimer, I don't know if this is ideal or will work for you, but it
works on my box. :)




On May 9, 4:20 pm, Jim Geurts <jgeu...@gmail.com> wrote:
> Thanks for your help with this Richard.  I look forward to see what
> comes in the future for easier aliases.  In the mean time, I'll look
> into using a DTO and custom transformer that Ayende mentioned here:http://ayende.com/Blog/archive/2007/08/26/Partial-Object-Queries-With...

Richard Brown (gmail)

unread,
May 10, 2010, 8:16:08 AM5/10/10
to nhu...@googlegroups.com
I like that solution; it looks like a handy little utility class.

There's now a TransformUsing() method on IQueryOver (r4980), so you can
write:

Session.QueryOver<Tenant>()
.Select(x => x.Id, x => x.FirstName, x => x.LastName)
.TransformUsing(new PartialObjectTransformer<Tenant>(x => x.Id, x =>
x.FirstName, x => x.LastName))
.List<Tenant>()

I've also fixed the property aliasing to allow usage of the AliasToBean<T>
transformer, so you could also write:

Tenant tenantDto = null;
Session.QueryOver<Tenant>()
.Select(list => list
.Select(t => t.Id).WithAlias(() => tenantDto.Id)
.Select(t => t.FirstName).WithAlias(() => tenantDto.FirstName)
.Select(t => t.LastName).WithAlias(() => tenantDto.LastName))
.TransformUsing(Transforms.AliasToBean<Tenant>())
.List<Tenant>()

(I updated the blog with an example here:
http://nhforge.org/blogs/nhibernate/archive/2009/12/17/queryover-in-nh-3-0.aspx#Projections)

Finally, another option is to use the LINQ provider to generate the partial
classes/DTOs. Something like this should work:

from t in s.Query<Tenant>()
select new Tenant
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,

Jim Geurts

unread,
May 10, 2010, 8:33:51 AM5/10/10
to nhusers
Awesome, thanks for following up on this!

On May 10, 7:16 am, "Richard Brown \(gmail\)"
<fluke...@googlemail.com> wrote:
> I like that solution; it looks like a handy little utility class.
>
> There's now a TransformUsing() method on IQueryOver (r4980), so you can
> write:
>
> Session.QueryOver<Tenant>()
>     .Select(x => x.Id, x => x.FirstName, x => x.LastName)
>     .TransformUsing(new PartialObjectTransformer<Tenant>(x => x.Id, x =>
> x.FirstName, x => x.LastName))
>     .List<Tenant>()
>
> I've also fixed the property aliasing to allow usage of the AliasToBean<T>
> transformer, so you could also write:
>
> Tenant tenantDto = null;
> Session.QueryOver<Tenant>()
>     .Select(list => list
>         .Select(t => t.Id).WithAlias(() => tenantDto.Id)
>         .Select(t => t.FirstName).WithAlias(() => tenantDto.FirstName)
>         .Select(t => t.LastName).WithAlias(() => tenantDto.LastName))
>     .TransformUsing(Transforms.AliasToBean<Tenant>())
>     .List<Tenant>()
>
> (I updated the blog with an example here:http://nhforge.org/blogs/nhibernate/archive/2009/12/17/queryover-in-n...)
Reply all
Reply to author
Forward
0 new messages