Playing with NHibernate - Inverse and Cascade Mapping Attributes
I have to admit that NHibernate provides a really flexible way of handling class inheritance and parent-child relationship.
Join the DZone community and get the full member experience.
Join For FreeAlthough I'm a big fan of Entity Framework, I have to admit that NHibernate provides a really flexible way of handling class inheritance and parent-child relationship. That said, I've noticed that these two important concepts are not very well explained in the documentation, which leads to a several discussions and debates in the community about the proper way of mapping your tables (and sometimes to explanations that state the opposite).
Consider the explanation of the inverse mapping (taken from theNHibernate docs):
Inverse: (optional - defaults to false) mark this collection as the "inverse" end of a bidirectional association.
If you think this explanation is incomplete, that makes two of us. There are some examples of the concept in the docs, but I couldn't find one that fully explains what this concept means and how you can use it together with thecascade (another mapping attribute). In the next few lines, I'm going to (try to) do this.
Scenario - One-to-many relationship
Let's define a simple scenario in which you have Products and Categories. And one Category can have many Products (i.e. a one-to-many relationship). In this case, the Database diagram and the model classes should look similar to this:
Basic mapping (default values)
Next, we are going to create mappings for these classes. Let's start with the default mapping values, just to see how NHibernate behaves as is:
We are going to run a simple test case that creates a Category with 3 Products and saves everything in the DB. For this, we are setting theProduct.Category property of each product before saving the data. Notice that we need to do everything ourselves: we have to associate the Category on each Product and we also have to save each entity in the session separately (and in a specific order, as we will be see later):
Note: you can find the solution with all the test cases here.
As you can see in the image above, NHibernate generates the expected SQL statements (3 INSERTS). But there is an important caveat on this approach: you need to make sure that you're saving the entities in the right order. If you save the Products in the session before saving the Category, when committing the transaction NHibernate will behave as follows:
- First, it will execute an INSERT statement for each Product, setting NULL in the CategoryId (which will only work if this column is nullable).
- Then, it will execute an INSERT statement to save the Category.
- Finally, it will execute an UPDATE statement for each Product to set the newly generated CategoryId.
Usually you should try to avoid doing things on your own, like saving the entities in the session in the right order or associating each Product with its Category individually (that's why we use an ORM in the first place, right?). To make things easier, NHibernate can handle this. In the next few lines we'll to remove some of our repetitive code by taking advantage of theInverse and Cascade mapping attributes.
Setting Inverse to 'false' (default)
Let's analyze the way we are associating the Products with the Category. I know that some folks will prefer to add the 3 Products inside theCategory.Products collection instead of setting the Category on each individual Product. What if I tell you that with the current mapping you can use both approaches? This is because we set the Inverse attribute in theCategory.Products mapping to false (default value). So what Inverse does? It tells NHibernate that the Parent is responsible (or not) of saving the association to its childs. The inverse=false
mapping means that when you save the Category you will also save the association of each Product that is inside the Category.Products collection. In constrast, setting this value to true basically means that "the Parent does not have the responsibility of saving the association".
Check the following code. Notice that instead of setting theProduct.Category property we are now adding the Product to theCategory.Products collection (and it works!):
Although we've reduced the code a little bit, there are two problems with this approach (if we use the current mapping):
- To save the association, NHibernate will use an INSERT/UPDATE strategy for each Product (same as before), which won't work if we have a null constraint on the Product.CategoryId column.
- Inverse can be used only to save entity association info, not data. Hence, we still need to save each item individually. If we save only the Category (the Parent), NHibernate will throw the followingTransientObjectException:
A way to improve this approach is by setting the Cascade mapping attribute to a value different than 'none' (default). And that's what we are going to do in the next section.
Setting Cascade mapping attribute
mapping means that when you save the Category you will also save the association of each Product that is inside the Category.Productscollection. In constrast, setting this value to true basically means that "the Parent does not have the responsibility of saving the association". TheCascade mapping attribute helps NHibernate to decide which operations should be cascaded from the Parent object to the Child object. Collections mapped with a value different than 'none' will perform extra tasks in addition to saving the entity. For instance, you can set the collection withcascade=save-update
, which means that when the object is saved/updated, NHibernate will check the associations and save/update any object that require it (for a complete explanation of all cascade values go here)
Let's update the mappings of the Category class by setting the cascadevalue to all:
Now, we can safely remove the code that saves the products individually. By only saving the Category in the session will be enough since now we've instructed NHibernate to save the products "in cascade".
Again, NHibernate will use the INSERT/UPDATE technique, which means that this approach won't work if the Product.CategoryId column is not-nullable.
Setting Inverse to 'true'
If you are facing the 'not-nullable' scenario, consider changing the Inverseproperty to true. Which means that the Category is no longer responsible to manage of the relationship. Then, update your code to associate the Products and the Categories using the Products.Category property (because the Product class is now the only owner of the association). Finally, you only need to save the Category in session (as before).
Summary
To sum up what we've explained:
- The Inverse attribute tells NHibernate if the collection is responsible to manage the relationship.
inverse=false
means that it should update the relationship, whileinverse=true
means that 'it does not have to do anything'. - The Cascade attribute helps NHibernate to decide which operations should be cascaded from the parent object to the child object. For instance,
cascade=save-update
tells NHibernate that when the Parent is saved/updated, it also needs to needs to insert the Parent's childs. - Depending on the scenario you should decide which value to use on these two properties. For instance:
- (one-to-many scenario) If your foreign-key allows nullable values, you can use a collection with
inverse=false
and a cascade value different than 'none'. When you save the Parent, NHibernate will take care of saving both childs and association. - (one-to-many scenario) If you have a not-nullable constraint in the DB, you can use a collection with
inverse=true
and a cascade value different than 'none'. In this case, you'll only need to associate the child with its parent in code before saving the parent.
- (one-to-many scenario) If your foreign-key allows nullable values, you can use a collection with
Published at DZone with permission of Mariano Vazquez, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments