views:

103

answers:

2

Let's say I have an entity called MyItem. It can be included in many "parents", like SomeCollection and SomeOtherCollection. Because it can be included in many parents, and since I don't want MyItem to know about the parents, I'd like to not have any properties in MyItem referencing a parent.

And since a parent, like SomeCollection, can contain many many MyItems, I feel like I need to have some sort of paging involved in getting the children from a parent. This would keep me from having a property in SomeCollection referencing MyItems. Lazy loaded or not, it's always "all or nothing" (right?).

I definitely need some reference between MyItem entities and their parents though, in the form of a mapping table in the database.

Questions:

  • How do I create mappings for this? Can I have mappings, or should the relation be kept in the business logic instead?
  • How would I query which MyItem entities exist in SomeCollection? Can I do this with only one trip to the database using ICriteria?
+1  A: 

Many-to-One
Parent contains a property Child, the child may be linked from several parents.

class Parent
{
    public virtual MyItem Child { get; set; }
}

<class name="Parent">
    <many-to-one name="Child" column="MyItemId" />
</class>

Many-to-Many with a join table
Parent contains a collection of Children, the children may be linked from several parents.

class Parent
{
    public virtual IList<MyItem> Children { get; set; }
}

<class name="Parent">
    <bag name="Children" table="parent_myitem">
        <key column="parentid" />
        <many-to-many class="MyItem" column="MyItemId" />
    <bag>
</class>

Criteria Querying

// find Parent with child named "foo".
DetachedCriteria.For<Parent>()
    .CreateAlias("Child", "c")
    .Add(Restrictions.Eq("c.Name", "foo"));

// find Parent with particular child
DetachedCriteria.For<Parent>()
    .Add(Restrictions.Eq("Child", child ));


// find Parent with one of children named "foo".
DetachedCriteria.For<Parent>()
    .CreateAlias("Children", "c")
    .Add(Restrictions.Eq("c.Name", "foo"));

// find a "page" of children for a parent
DetachedCriteria.For<Parent>()
    .Add(Restrictions.Eq("Id", parent.Id ))
    .CreateAlias("Children", "c")
    .SetFirstResult( 1041 )
    .SetMaxResults( 20 )
    .GetExecutableCriteria( session )
    .List<MyItem>();

That last query may or may not be more efficiently done by using just lazy-loading the whole children collection on first access, and then indexing into it on subsequent "pages". It depends on your data and usage.

Unless I knew a priori that the child collections will be gigantic, I would go the lazy load route first. If timings and profiling show serious slowness, then I would switch to the Criteria method.

Lachlan Roche
This is how I interpret your answer regarding paging:If I set lazy load to false, I will not use the property of the parent, but rather execute a criteria like your "page" one to get a page of the parent's children.An alternative is to use lazy loading with a batch size. Then if I use the Children property, "batch-size" children would be fetched at a time. So (for a batch-size of 20) getting children 41 to 60 would cause three trips to the database, each resulting in 20 items.Is that a correct interpretation of your answer?
Kristoffer
@Kristoffer use lazy="true" if you don't want the Children loaded with Parent.
Lachlan Roche
@Kristoffer I have just run some tests, and my advice was wrong about batch-size and collections. Answer has been updated accordingly.
Lachlan Roche
A: 

I have had a case like yours. I had Configuration class which can be either Global Configuration, Project Specific Configuration, or User Specific Configuration. What I did was this:

  1. I map the Parent like I normally would in any case to all possible parents (except global, it means applied to all so no parents)
< class name="ConfigurationDomain" table="configuration">  
    < property name="ProjectId" column="project_id" type="int" insert="false" update="false" />  
    < property name="UserId" column="user_id" type="int" insert="false" update="false" />  
    < many-to-one name="Project" column="project_id" lazy="false" />  
    < many-to-one name="User" column="estimator_id" lazy="false" />
< /class>  
  1. I map the configuration as collection in every possible parents
< class name="UserDomain" table="user">  
    < set name="ConfigurationList" lazy="true" cascade="all-delete-orphan">  
      < key column="user_id" />  
      < one-to-many class="ConfigurationDomain" />  
    < /set>  
< /class>  

< class name="ProjectDomain" table="user">  
    < set name="ConfigurationList" lazy="true" cascade="all-delete-orphan">  
      < key column="project_id" />  
      < one-to-many class="ConfigurationDomain" />  
    < /set>  
< /class>

Just like that and it worked for me. How do I access configuration of, say, a user with id 55 is this:

I don't use someUser.ConfigurationList since it's slow. I only map the parent so that I can do this in HQL (it's much faster):

select c from ConfigurationDomain c where c.UserId=55

And to get Global Configuration I would do this:

select c from ConfigurationDomain c where (c.UserId IS NULL) and (c.ProjectId IS NULL)

On reflection, I think you can even remove the collection mapping if you decide to use HQL.

NOTE: I've used Criteria in my early days with NHibernate, but then I found HQL is somewhat more powerful for me, other might have different opinion in this.

Jaya Wijaya