I'm having trouble getting my head around the way I should implement an ordered child relationship with NH.
In the code world, I have:
class Parent
{
public Guid Id;
public IList<Child> Children;
}
class Child
{
public Guid Id;
public Parent Parent;
}
A Parent has a list of Child[ren] with an order. In reality, the Children collection will contain unique Childs which will be enforced by other code (i.e. it will never be possible to add the same child to the collection twice - so i dont really care if the NH collection enforces this)
How should I implement the mappings for both classes?
From my understanding:
Bags have no order, so i dont want this
Sets have no order, but i could use order-by to do some sql ordering, but what do i order by? I can't rely on a sequential ID. so i dont want this?
Lists are a duplicate-free collection, where the unique-key is the PK and the index column, so i do want this?
So, using a list, i have the following:
<list cascade="all-delete-orphan" inverse="true" name="Children">
<key>
<column name="Parent_id" />
</key>
<index>
<column name="SortOrder" />
</index>
<one-to-many class="Child" />
</list>
When I insert a parent which a child on it, i see the following SQL:
Insert into Child (id, Parent_id) values (#p0, #p1)
I.e, why doesn't it insert the SortOrder?
If I do a SchemaExport the SortOrder column is created on the Child table.
:(
If I set Inverse="false" on the relationship, i see the same SQL as above, followed by:
UPDATE "Child" SET Parent_id = #p0, SortOrder = #p1 WHERE Id = #p2
Why does it still INSERT the Parent_id with inverse="false" and why doesn't it insert the SortOrder with inverse="true"?
Am I approaching this totally wrong?
Is it also true that assuming this was working, if I were to do:
parentInstance.Children.Remove(parentInstance.Children[0]);
save the parent and reload it, that the list would have a null in position 0, instead of shuffling the rest up?
Thanks
Inverse=true means that NHib will not try to save the actual collection. It will however still cascade the save operation through the collection onto the contained entities, which includes persisting transient instances. This is why you get an insert with no SortOrder - NHib is persisting your transient Child object.
There was a similar question where the solution involved moving to <bag> mappings, but that loses the ordering qualities that <list> has.
Now I've used a <list> before with a <many-to-many> mapping, and there it worked great. There were two SQL insert calls - one to the table containing the child entity and the other to the linking table. I suspect that in your '' case, NHib is still applying the same strategy even though both calls are to the same table.
And finally, if you remove the item at index 0 then you end up with a null value.
So overall, I'd suggest either: 1) move to a <bag> mapping for your collection, and maintaining a specific property for sort order; or 2) move to a <many-to-many> mapping inside your collection.
Related
On a delete context with nHibernate, when deleting a parent with child collection
I would like to know why Nhibernate do a delete line by line for children (on child PK)
DELETE FROM children where Id=1
DELETE FROM children where Id=2
...
DELETE FROM parent where id=1
Why nhibernate can't do
DELETE FROM children where parentId=1
DELETE FROM parent where id=1
It will be more efficient if parent have 100k children for example.
I search in many topics without finding a correct anwser. I did some tests too but witout success
An idea ?
That is a case, where we can use NHibernate extensibility points. The doc
19.3. Custom SQL for create, update and delete
NHibernate can use custom SQL statements for create, update, and
delete operations. The class and collection persisters in NHibernate
already contain a set of configuration time generated strings
(insertsql, deletesql, updatesql etc.). The mapping tags <sql-insert>,
<sql-delete>, and <sql-update> override these strings:
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
So, if standard deletion is not useful, we can provide our own process, including some stored procedure
<sql-delete>exec deletePerson ?</sql-delete>
Summary, in most cases, the standard model is working and effective enough. In case we need to improve SQL .. we can ...
Here's my problem: I have a set of ids. These are the ids of a collection of root entities. Now I want to delete all these root entities, efficiently.
I can't do a WHERE Id IN (1, 2, 3) type of clause, as I'm deleting root entities with children.
I'm wondering if it's possible to avoid retrieving all root entities and deleting them one by one. The problem with that approach isn't so much the SELECT, it's have lots of separate DELETE statement.
Is it possible for NHibernate to batch this, including the delete of all the children? Extra complexity: children can have their own children.
So I'd want NHibernate to first delete the 'bottom-most' children with an IN-clause (maybe multiples ones), then the children with an IN-clause, and then finally the root entities with an IN-clause.
If this isn't possible, what's a good approach to delete multiple root entities efficiently with NHibernate?
UPDATE
This is not valid in your case where you had a list of ids (didn't read your question properly).
First, batch deletes of the "parents" can be made setting adonet.batch_size, http://nhibernate.info/doc/nh/en/index.html#performance-batch-updates.
END UPDATE
Secondly, to avoid reading the children (and generate deletes for these), you can set the cascading delete to happen on db level by setting ON CASCADE DELETE as a foreign key constraints.
Eg (note the on-delete attr)
<set name="TheCollection" inverse="true" cascade="save-update">
<key name="theKey" on-delete="cascade"/>
<one-to-many class="TheType"/>
</set>
Note that
this only works on inverse (bidirectional) relationships.
your db schema needs to be updated with this fk constraint
don't use cascade="all" or similar where deletes are included
Might help using the Session.Delete statement passing an array of IDs? I believe this will delete the whole map for those objects matching the IDs in the array.
Session.Delete("from MyTable t where t.ID in :IDs", IDs, NHibernate.NHibernateUtil.Guid);
where IDs is an array of Guids in this case, but could be any object.
when I use one-to-many relations, I keep getting:
collection is not associated with any session
errors when loading eagerly, and not getting any children if I load lazily.
when using many-to-many, I get unnecessary and wrong joins.
The data scheme is like this:
Item:
int ID (PK)
string Name
int StorageId (FK Storage, non-unique usage)
Storage (ignored in mapping):
int ID (PK)
string Name
TransporterToStorage (contains info, not only link table):
int ID (PK)
int StorageId (FK Storage)
int TransporterId (FK Transporter)
string TransportLineName
Transporter (ignored in mapping):
int ID (PK)
string Name
Item shall be represented by a C# / NHibernate class, with an additional collection property TransportersToStorage, containing 0-n entries. Due to performance reasons, I want to map only the Item.StorageId to the TransporterToStorage.StorageId and ignore the Storage table, which holds the primary key for both.
<bag name="TransportersToStorages" ... >
<key property-ref="StorageId" unique="false" ... />
<one-to-many class="TransporterToStorage" column="StorageId" ... />
</bag>
one-to-many seems to violate some NHibernate rules, because equal TransporterToStorage entries can belong to multiple Items. This is probably the cause of the:
collection is not associated with any session
error on eager loading.
The specific query is like (quick pseudo HQL, but actually done with Criteria):
select Item inner join fetch TransporterToStorage tts
where tts.TransporterId = :p1 and tts.StorageId in (:p2, :p3, :p4)
Can many-to-many or a similar association be done with only one join in the SQL sent to the database?
What is best to map this?
The child collection shall be loaded lazily by default (meaning: not at all), and eagerly in the given case.
I suspect a session management problem here, I suspect your session is closed BEFORE you access your lazy/eager loaded children. You might need to change the way you manage your sessions or load parent/children into a DTO and return this from your method.
Without knowing more about the way you are handling sessions a more definitive answer is difficult.
I have this setup: Parent, with a collection of Child.
class Parent {
IList<Child> Childs { get; set; }
}
HQL:
("From Parent").Future();
("From Child").Future();
foreach(Parent p in result) {
foreach(Child c in p.Childs) {
}
}
This gives the classic N+1 problem. Two SQL statements are sent to server in 1 roundtrip, so all the data exists in first level cache, so why does NH still exists SQL for every child.
Version 3.1.0.400
When you execute the future query, you pull all Parent and Child objects into the 1st-level cache. The Parent objects contain a lazy collection, which needs to be populated. To populate the collection, NHibernate has to query the database. (We'll get to why in just a second.) The query returns Child objects and those child objects are already in the L1 cache. So those objects are used to populate the collection.
Now why does NHibernate have to query the database to populate the Childs collection? You could have a "where" clause on the collection that filters out Child objects with IsDeleted==true. You could have code in an EventListener that filters out certain Child objects. Basically there is a lot that can happen and NHibernate can't make any assumptions about the relationship between Parent and Child objects.
You can give it enough information by specifying a fetching strategy in the HQL or in your mapping. In HQL, you could write:
var parents = session.CreateQuery("from Parent p join fetch p.Childs").Future<Parent>();
The Child object query using the future would be completely optional as you're fetching the children with the parents. Because of the join fetch, you will get duplicate Parent objects, though they'll be the same object. (You're doing an inner join in the database and returning one copy of the parent row for each child row.) You can get rid of these by iterating over parents.Distinct().
If you always want to fetch Child objects with the corresponding Parent, you can also use fetch="join" in your Parent mapping.
<bag name="Children" cascade="all-delete-orphan" fetch="join">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>
If neither of these options works for your scenario, you can specify batch-size on the collection mapping. You will still execute a database query when you hit parent.Childs, but NHibernate will eagerly initialize any other collection proxies.
<bag name="Children" cascade="all-delete-orphan" batch-size="10">
<key column="ParentId"/>
<one-to-many class="Child"/>
</bag>
I have been trying to get to grips with Hibernate's inverse attribute, and it seems to be just one of those things that is conceptually difficult.
The gist that I get is that when you have a parent entity (e.g. Parent) that has a collection of Child objects using a one-to-many mapping, setting inverse=true on the mapping tells Hibernate that 'the other side (the Child) has responsibility to update itself to maintain the foreign key reference in its table'.
Doing this appears to have 2 benefits when it comes to adding Children to the collection in your code, and then saving the Parent (with cascade-all set): you save an unneccessary hit on the database (because without inverse set, Hibernate thinks it has two places to update the FK relationship), and according to the official docs:
If the column of a
association is declared
NOT NULL, NHibernate may cause
constraint violations when it creates
or updates the association. To prevent
this problem, you must use a
bidirectional association with the
many valued end (the set or bag)
marked as inverse="true".
This all seems to make sense so far. What I don't get is this: when would you NOT want to use inverse=true on a one-to-many relationship?
As Matthieu says, the only case where you wouldn't want to set inverse = true is where it does not make sense for the child to be responsible for updating itself, such as in the case where the child has no knowledge of its parent.
Lets try a real world, and not at all contrived example:
<class name="SpyMaster" table="SpyMaster" lazy="true">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Name"/>
<set name="Spies" table="Spy" cascade="save-update">
<key column="SpyMasterId"/>
<one-to-many class="Spy"/>
</set>
</class>
<class name="Spy" table="Spy" lazy="true">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Name"/>
</class>
Spymasters can have spies, but spies never know who their spymaster is, because we have not included the many-to-one relationship in the spy class. Also (conveniently) a spy may turn rogue and so does not need to be associated with a spymaster. We can create entities as follows:
var sm = new SpyMaster
{
Name = "Head of Operation Treadstone"
};
sm.Spies.Add(new Spy
{
Name = "Bourne",
//SpyMaster = sm // Can't do this
});
session.Save(sm);
In such a case you would set the FK column to be nullable because the act of saving sm would insert into the SpyMaster table and the Spy table, and only after that would it then update the Spy table to set the FK. In this case, if we were to set inverse = true, the FK would never get updated.
Despite of the high-voted accepted answer, I have another answer to that.
Consider a class diagram with these relations:
Parent => list of Items
Item => Parent
Nobody ever said, that the Item => Parent relation is redundant to the Parent => Items relation. An Item could reference any Parent.
But in your application, you know that the relations are redundant. You know that the relations don't need to be stored separately in the database. So you decide to store it in a single foreign key, pointing from the Item to the Parent. This minimal information is enough to build up the list and the reference back.
All you need to do to map this with NH is:
use the same foreign key for both relations
tell NH that one (the list) is redundant to the other and could be ignored when storing the object. (That is what NH actually does with inverse="true")
These are the thoughts which are relevant for inverse. Nothing else. It is not a choice, there is only one way of correct mapping.
The Spy Problem:
It is a completely different discussion if you want to support a reference from the Item to the Parent. This is up to your business model, NH doesn't take any decisions in this. If one of the relations is missing, there is of course no redundancy and no use of inverse.
Misuse: If you use inverse="true" on a list which doesn't have any redundancy in memory, it just doesn't get stored. If you don't specify the inverse="true" if it should be there, NH may store the redundant information twice.
If you want to have an unidirectional association i.e. that the children can't navigate to the Parent. If so, you FK column should be NULLABLE because the children will be saved before the parent.