Partial Object Queries With NHibernate
Aaron still wants partial object queries, so I set up to build them using NHibernate. Here is the implementation, notice that this query will result in a list of Blog instances, but the select will only include their titles & subtitles.
using (ISession session = sessionFactory.OpenSession()) { TupleToPropertyResultTransformer transformer = new TupleToPropertyResultTransformer(typeof(Blog),"Title", "Subtitle"); IList list = session.CreateQuery("select b.Title, b.Subtitle from Blog b") .SetResultTransformer(transformer) .List(); foreach (Blog b in list) { System.Console.WriteLine("Blog: {0} - {1}", b.Title, b.Subtitle); } }
But where does TupleToPropertyResultTransformer comes from, well, that is where the magic comes in, here is my implementation for it:
public class TupleToPropertyResultTransformer : IResultTransformer { private Type result; private PropertyInfo[] properties; public TupleToPropertyResultTransformer(Type result, params string[] names) { this.result = result; List<PropertyInfo> props = new List<PropertyInfo>(); foreach (string name in names) { props.Add(result.GetProperty(name)); } properties = props.ToArray(); } public object TransformTuple(object[] tuple, string[] aliases) { object instance = Activator.CreateInstance(result); for (int i = 0; i < tuple.Length; i++) { properties[i].SetValue(instance, tuple[i], null); } return instance; } public IList TransformList(IList collection) { return collection; } }
This isn't the most optimized version that you can think of, but it does the job.
I want to make it clear, however, that I feel that doing stuff like this is not something that I would consider to be a best practice. Quite the opposite, frankly. What we have here is an object in a state that it was never intended to be, with only part of its fields filled, and certainly not based on any logic. I would much rather see a DTO class take its place, because that has a clear responsibility in the application, reusing your entities as dumb data container is not something that I would recommend.
Comments
Wouldn't the AliasToBeanResultTransformer do exactly the same thing?
I was trying to get NHibernate's projections to return hierarchial
DTO's, that is a DTO child objects and setting the child object's properties, and also collections, so you could get NHibernate to fill something like MyDTO.MyChildDTO.Name and MyDTO.DTOChildren[0].Name (and of course MyDTO.MyChildDTO.MyChildDTO2.Name and MyDTO.DTOChildren[0].MyChildDTO.Name).
I couldn't find any other way to do this than writing a custom IResultTransformer.
Do you know if NHibernate
would support this out-of-the-box or is a custom IResultTransformer the way to go ?
Interesting to know that its possible but as you say its probably something you want to avoid.
The AliasToBeanResultTransformer is built to be used with a criteria.
I would suggest building a customer result transformer for that, it is very simple,and you can have it do it your way
While the code is cool, I agree with you on the point that this leads to fuzzy-looking objects.
Picture for example you have an .Equals() implementation that checks those 2 properties + the author's name. The author's name isn't loaded so it needs to trigger a refresh.
So now if we use a semi-harmless method like .Contains() we will likely have introduced one of the leakiest abstractions that we could possibly accomplish. Picture this...
if(blogs.Contains(partialBlogInstance))
{
//do something
}
This code will now either have the strange side effect of going to the database --or -- throwing an exception because we've lost session at this point.
I can't see why someone would really want to do this. Write a DTO for things like dropdowns and pull those out of your tuple arrays.
Thank you for your time Ayende.
Yeah, the transformer was simple to build, except for the collections.
Now the transformer works by transforming tuples first to a hiearchy
where the DTO's collections contain only one item, and then consolidates
the collections in the TransformList method to a distinct root DTO which has to provide some business ID to group the collections. A dirty solution was to use GetHashCode() for now...
I didn't read your code careful enough to realize you were using the transformer with HQL queries.
Nice ;) I was under the impression the transformers wouldn't work
with HQL queries (although supported) because I couldn't get the built-in transformers to work (mainly the AliasToBeanResultTransformer). Now I know why I ;)
Register your own WordPress blog here!
A year ago i made something similar in a prj.
I port it to uNhAddIns naming it PositionalToBeanResultTransformer.
Apparently is not a best practices but, if more than one developer need it, it is useful.
;)
Bye.
Fabio.
Comment preview