views:

718

answers:

3

I'm starting out with nHibernate and have a simple example that I cannot get working as I'd like.

I have two model objects (Blog and Posts) and I would like to load them all in a single query for one scenario. I want lazy loading in other cases.

I naively thought that I could write something like this:

var blogs = session.Linq<Blog>().Expand("Posts");

But this will give me an instance of blog for every post rather than adding the posts to the blog.

I know I'm doing something stupid. Can someone please point out what it is? Is it that I need to relate the post and blog entities in my linq query?

Code and Mappings:

public class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }
    public virtual long Identifier { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
    public virtual Post AddPost(Post post)
    {
        post.Blog = this;
        Posts.Add(post);
        return post;
    }
}


public class Post
{
    public virtual long Identifier { get; set; }
    public virtual string Name { get; set; }
    public virtual Blog Blog { get; set; }
}

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nhibEx" namespace="nhibEx">
  <class name="Blog" lazy="true">
    <id name="Identifier">
      <generator class="native" />
    </id>
    <property name="Name" not-null="true" length="100"/>
    <set name="Posts" inverse="true" cascade="save-update" lazy="true">
      <key column="BlogIdentifier" foreign-key="fk_Post_Blog"/>
      <one-to-many class="Post"/>
    </set>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nhibEx" namespace="nhibEx">
  <class name="Post" lazy="true">
    <id name="Identifier">
      <generator class="native" />
    </id>
    <property name="Name" not-null="true" length="255"/>
    <many-to-one name="Blog" column="BlogIdentifier" class="Blog" />
  </class>
</hibernate-mapping>
+1  A: 

Distinct is what you need...

Edit: When it doesn't work: do the distinct after the tolist. I don't know why NHibernate loads the same number of objects as the number of database records returned and doesn't do the distinct automatically. This issue/feature is not Linq specific, but will also happen when you use criteria or hql.

session.Linq<Blog>().Expand("Posts").ToList().Distinct();

Sometimes it can be more efficient to execute 2 queries (seperate, or using multiquery/future) than executing one query with a left outer join.

Paco
Hi Paco, thanks for the reply but I'm confused why I would need this? I tried it anyway and it doesn't seem to work?
LostSilver
@LostSilver: See edit
Paco
Thanks Paco and Alexey. You're right this does the trick. I will post on the other forum to try to understand why it behaves in this way.
LostSilver
A: 

Hi LostSilver. We have just the same problem. It seems to me that linq is always in eager loading mode. So you don't need to do exapnd. However it is very bad. Have you tried to contact HN guys in their google group?

Alexey Zakharov
Linq is not always in eager loading mode.
Paco
+4  A: 

After searching other forums (perhaps I should of done this properly first!) I'm using this solution:

var blogs = session.Linq<Blog>();
blogs.QueryOptions.RegisterCustomAction(
criteria => criteria.SetResultTransformer(new DistinctRootEntityResultTransformer()));
var results = blogs.Expand("Posts");

I didn't want to use Distinct as I wanted to return IQueryable

Seems to work. I just need to know the theory :)

http://nhforge.org/wikis/howtonh/get-unique-results-from-joined-queries.aspx

LostSilver
Thanks for posting this answer
Paco
This is not a solution: it will return less results if you use LIMIT x,y and removes duplicates on the code side.
HeavyWave