nHibernate Self Join Mapping - nhibernate

This is probably incredibly simple, but I just cant see the wood for the trees at the moment.
For brevity, I would like to model a word object, that has related words to it (synonyms), In doing so I could have the following mappings:
<class name="Word" table="bs_word">
<id name="Id" column="WordId" type="Int32" unsaved-value="-1">
<generator class="native">
<param name="sequence"></param>
</generator>
</id>
<property name="Key" column="word" type="String" length="50" />
<many-to-one name="SynonymGroup" class="BS.Core.Domain.Synonym, BS.Core" column="SynonymId" lazy="false"/>
<class name="Synonym" table="bs_Synonym">
<id name="Id" column="SynonymId" type="Int32" unsaved-value="-1">
<generator class="native">
<param name="sequence"></param>
</generator>
</id>
<property name="Alias" column="Alias" type="String" length="50" />
<bag name="Words" cascade="none" lazy="false" inverse="true">
<key column="SynonymId" />
<one-to-many class="Word" />
</bag>
Mapping it like this would mean for a given word, I can access related words (synonyms) like this:
word.SynonymGroup.Words
However I would like to know if it is possible to map a bag of objects on an instance of a word object...if that makes sense, so I can access the related words like this:
word.Words
I've tried playing around with the map element, and composite elements, all to no avail - so I was wondering if some kind person could point me in the right direction?
ta,
kmoo01

This would map a Word entity with a collection of Words (Synonyms):
<class name="Word">
<id ...>
<generator .../>
</id>
<set name="Synonyms" cascade="all">
<key />
<many-to-many class="Word" />
</set>
</class>
You can customize table names in the class and set elements, and column names in the key and many-to-many elements as needed.
Note that I've used set instead of bag, as it fits the semantics better. You can map it to an ICollection<Word> or Iesi.Collections.Generic.ISet<Word>.

Related

Yet another one on "deleted object would be re-saved by cascade (remove deleted object from associations)"

Most all of the references to this NHibernate error (in the title) talk about child objects being deleted while still contained in a parent collection that would only re-save them later negating their "delete" (which is what the error states and the suggestion to remove object from associations that are re-saving it).
But in my case, the error happens while deleting the parent record (then the "child" is deleted via the association, so I don't want the association removed, as the error suggest)
It would have made sense if that happened every time, but I only observe this error intermittently, running exactly the same code but only against particular records. Other identical records don't trigger the condition and the delete goes through (all the time I deal with one Customer with two address-es.)
Not sure how much and what code to show. This is my "parent" object mapping:
I've got a OneCustomer-to-ManyAddresses relation mapped as a "set" of CustomerAddress "composite elements"
<class name="Customer" table="Customer">
<id name="Id" column="[Id]" type="Guid">
<generator class="guid" />
</id>
...
<set name="AddressList" lazy="true" table="CustomerAddress" cascade="all-delete-orphan" >
<key column="[CustomerId]"/>
<composite-element class="CustomerAddress">
<parent name="Customer"/>
<many-to-one name="Address"
class="Address"
column="[AddressId]"
not-null="true"
cascade="all"/>
...
</composite-element>
</set>
And this is the mapping for the "composite" object CustomerAddress:
<class name="CustomerAddress" table="CustomerAddress" >
<id name="Id" column="[Id]" type="Guid">
<generator class="guid" />
</id>
<many-to-one
name="Address"
column="[AddressId]"
class="Address"
not-null="true"
cascade="all"/>
<many-to-one
name="Customer"
column="[CustomerId]"
class="Customer"
not-null="true"
cascade="all"/>
...
</class>
What is the explanation of the error and its intermittent nature?
ADDITIONAL INFO
After further examination - looking for another collection that may hold a reference to same "Address" child, I saw the following mapping:
<class name="Address" table="Address">
<id name="Id" column="[id]" type="Guid" >
<generator class="guid" />
</id>
<!-- REMOVING THE FOLLOWING <BAG> SEEMS TO BE FIXING MY ISSUE -->
<bag name="CustomerAddressList" inverse="true" cascade="none" lazy="false" >
<key>
<column name="[AddressId]" />
</key>
<one-to-many class="CustomerAddress" />
</bag>
</class>
Removing the <bag name="CustomerAddressList"... seems to fix my issue. Explanation?
I'm going to have a guess:
You have a bi-directional association, but you haven't specified that one of them is the non-owner (by setting "inverse = true").
Try modifying the CustomerAddress relationship as follows:
<class name="CustomerAddress" table="CustomerAddress" >
<id name="Id" column="[Id]" type="Guid">
<generator class="guid" />
</id>
<many-to-one
name="Address"
column="[AddressId]"
class="Address"
not-null="true"
cascade="all"/>
<many-to-one
name="Customer"
column="[CustomerId]"
class="Customer"
not-null="true"
cascade="all"
inverse="true"
/>
...
</class>
why do you have two mappings for CustomerAddress table (one as composite Element and one as Entity)? remove the entity mapping for Customer address and it should work.

Trying to map several bags with the same table - failed to lazily initialize a collection of role exception

I have a problem with mapping in nhibernate. I am using nhibernate 2.2 version.
Seems like the problem is in mapping but I am not sure this is the cause. Anyway, I have two tables which I would like to map. I created a hbm file for first table and a data transfer object too. All columns were mapped and everything works fine here.
But, now I want to add three bags to this class, which will point to the same table, my second table which I'd like to connect with. I created bags and mapped everything but when I am retrieving my data only one of these bags is filled, and the other ones are left empty, and I get an error "failed to lazily initialize a collection of role: com.organic.mitsu.hib.ModelContent.options - no session or session was closed". And I am 100% sure that my data in database are good. When I remove two bags from my mapping everything works fine, with only one bag left. Here is the hbm file:
<class name="MyFirstClass" table="MyFirstTable">
<id name="ID">
<generator class="native" />
</id>
<property name="ItemOne" />
<property name="ItemTwo" />
<property name="ItemThree" />
<property name="ItemFour" />
<bag name="FirstItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemOne"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
<bag name="SecondItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemTwo"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
<bag name="ThirdItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemThree"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
How should I solve the problem? Is this even possible to do it like this?
And here is the mapping for the MySecondTable:
<class name="Item" table="MySecondTable">
<id name="ID">
<generator class="assigned" />
</id>
<property name="ItemID" />
<property name="Language" />
<property name="Value" />
Actually, the original thing that I was trying to map is with composite element and without the mapping for MySecondTable. I only have a dto class Item, with ItemID and Value columns. I got the same error and the mapping looks like this:
<class name="MyFirstClass" table="MyFirstTable">
<id name="ID">
<generator class="native" />
</id>
<property name="FirstItem" />
<property name="SecondItem" />
<property name="ThirdItem" />
<bag name="FirstItemNames" table="MySecondTable">
<key column="ItemID" property-ref="FirstItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
<bag name="SecondItemNames" table="MySecondTable">
<key column="ItemID" property-ref="SecondItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
<bag name="ThirdItemNames" table="MySecondTable">
<key column="ItemID" property-ref="ThirdItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
Sounds like the SecondItems and ThirdItems are are being fetched lazily after the session was closed, which is not allowed. You need to either force the fetching while the session is active or change the mappings so that lazy fetch (the default) is turned off.
See here for more details.

Hibernate - Restriction for class in a list

I'm trying to pull back a list of items that have a specific type of item in a set.
For example:
<class name="Owner" table="OWNER">
<id name="id" column="OWNER_ID" />
<set name="cats" table="OWNER_CATS" lazy="false">
<key column="OWNER_ID" />
<many-to-many class="Cat" />
</set>
<class name="Cat" table="CAT" discriminator-value="C">
<id name="id" column="CAT_ID" />
<discriminator column="type" type="character" />
<subclass name="Lion" discriminator-value="L">
<property name="teeth" />
</subclass>
</class>
Using restrictions how would I get a list of owners who have lions as pets?
I've tried something along the lines of the following to no avail:
criteria.createCriteria("cats").add(Restrictions.eq("class", Lion.class));
At the moment (though I'm not sure if this is intentional) the way this works is you have to specify the discriminator value as the restriction value, rather than the class itself, so:
criteria.createCriteria("cats").add(Restrictions.eq("class", "L"));
in your example.

Delete from <Map>

I have mapping:
<class name="User" table="Users" lazy="false">
<id name="id" type="Int32" column="id">
<generator class="identity" />
</id>
<property name="name" column="name" type="String"/>
<map name="Urls" table="UserUrl" lazy="true" inverse="true" cascade="all">
<key column="user_id"></key>
<index column="url_type_id" type="Int32"/>
<one-to-many class="UserUrl"/>
</map>
</class>
<class name="UserUrl" table="UserUrl" lazy="false">
<id name="id" type="Int32" column="id">
<generator class="identity"/>
</id>
<property name="user_id" column="user_id" type="Int32" not-null="true"/>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</class>
Also I get
class User
{
IDictionary<int,UserUrl> Urls;
....
}
User currentUser = FindById(2);
currentUser.Urls.Remove(5);
So I remove one item from assosiation collection of Url. Then I call SaveOrUpdateCopy(...), But url from table UserUrl doesn't delete and there is no error.
Does anybody know how to delete child item from collection and from DB?
Set inverse to false for <map> element.
I try search in google but there is no information about difference between <map><one-to-many > and <map><composite-element> so I use <map><one-to-many >.
<map name="Urls" table="UserUrl" lazy="true" cascade="all-delete-orphans">
<key column="user_id"/>
<index column="url_type_id" type="Int32"/>
<composite-element>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</composite-element>
</map>
If the Url is not an independent entity, having an own Id, then you could map itas composite-element. The url is treated as a value type.There is no class UserUrl mapping anymore.
I use UserUrl class in other code
Try this:
<map name="Urls" lazy="true" cascade="all-delete-orphans">
You don't need table, because the table is defined in the class UserUrl mapping
You should not make it inverse, if it is not
You should cascade it all-delete-orphan to tell NH to remove items that are removed from the collection.
Not related to your questions, why do you have this in the url?
<property name="user_id" column="user_id" type="Int32" not-null="true"/>
You are mapping the foreign key there! I would never ever dare to do this.
Actually, I'm not sure if you should not actually map it like this:
<map name="Urls" table="UserUrl" lazy="true" cascade="all-delete-orphans">
<key column="user_id"/>
<index column="url_type_id" type="Int32"/>
<composite-element>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</composite-element>
</map>
If the Url is not an independent entity, having an own Id, then you could map it as composite-element. The url is treated as a value type. There is no class UserUrl mapping anymore.
Edit:
See the NH Reference Chapter 7, it explains the components.
If you get not-null problems on references, just remove the not-null
constraint on the foreign key. NH needs to insert some columns to get
primary keys if you use generator class="identity", so it stores
a null temporary.
-Btw, don't answer to your question if you are actually commenting to an
answer. You could be down-voted
-The problem is that I have no stackoverflow account, so only one way to login to site on the same name is to enter name and email under my post. I try to register account on that site with the same name and email, but when I log in it treats me as new user (not as I login fist time).
So I have no choice.
Also I find out that working code is
<map name="Urls" lazy="true" cascade="all-delete-orphans" inverse="true">
without inverse="true" it doesn't work.

nhibernate newbe question ... easy one here

I'm new to nhibernate so this should be easy. I have a mapping file as below although I deleted some fields that aren't relevant to this question. The streamfields class contains a bag of fieldmappings. I want the join to be on field_no column but the sql that is sent is on the id field (str_fld_id") as seen below.
I see what the below sql is doing but it's not what I wanted. It's trying to query the field_mappings table based on the values found in the id column str_fld_id in the StreamFields class when I thought it was clear I wanted the field_no to be used on both ends. I say I thought it was clear because the mapping for the field_mapping class has the below attribute and they both have the same named field
Below is in my FieldMappings mapping file.
<many-to-one name="FieldNo" cascade="none" column="`Field_No`" not-null="true">
Sql sent
NHibernate: SELECT fkfieldmap0_.[field_no] as field5_1_, fkfieldmap0_.[Mapping_Id] as Mapping1_1_, fkfieldmap0_.[Mapping_Id] as Mapping1_3_0_, fkfieldmap0_.[Std_fld_Id] as Std2_3_0_, fkfieldmap0_.[Field_Position] as Field3_3_0_, fkfieldmap0_.[Field_No] as Field4_3_0_ FROM [Field_Mappings] fkfieldmap0_ WHERE fkfieldmap0_.[field_no]=#p0; #p0 = '20'
StreamFields mapping
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="DataTransfer.StreamFields,DataTransfer" table="`stream_fields`" lazy="true">
<id name="StrFldId" column="`str_fld_id`" type="int">
<generator class="native" />
</id>
<property type="int" not-null="true" name="FieldNo" column="`field_no`" />
<many-to-one name="StreamId" cascade="none" column="`stream_Id`" />
<bag name="FkFieldMappingsStreamFields" inverse="true" lazy="false" cascade="all">
<key column="`field_no`" />
<one-to-many class="DataTransfer.FieldMappings,DataTransfer"/>
</bag>
</class>
[Edited - with old comments]
Okay, i think i finally got you right and i might admit the problems i had understanding what you want took me a while and result of the lack of information you provided. In the future please provide the mapping of both tables clarify on the point wheather it is a mapping or a query issue. Thx.
IMO you have misunderstood the idea of a parent/child-relation.
The bag you mentioned like to have within the StreamFields class shouldn't be a bag but a direct association. Like this:
<class name="DataTransfer.StreamFields,DataTransfer" table="stream_fields" >
<id name="StrFldId" column="str_fld_id" type="int">
<generator class="native" />
</id>
<property type="int" not-null="true" name="FieldNo" column="field_no" />
<many-to-one name="FieldMapping" class="FiueldMapping" column="Field_No" />
</class>
This of course will only work if you have a property of type FiledMapping in your class.
You want to map FieldMapping to the column Field_No within StreamFields class. There can only be one value within this column, so a bag makes no sense at all. If you want to have a bag of course you can keep it the way it already worked but be aware that the 'key-column' within the bag refers to the child table - in an other way it makes no sense cause a ForeignKey has to map to a PrimaryKey on its parent table. This ensures it is unique and set.
I really don't want to rant but would strongly encourage you to review the hibernate reference about collection mapping to get a deeper clue however.
Hopely this will solve your problem.
Below are the mappings for the classes.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="DataTransfer.StreamFields,DataTransfer" table="`stream_fields`" lazy="true">
<id name="StrFldId" column="`str_fld_id`" type="int">
<generator class="native" />
</id>
<property type="string" length="50" name="FieldName" column="`field_name`" />
<property type="int" name="InputFieldPosition" column="`input_field_position`" />
<property type="int" name="Start" column="`start`" />
<property type="int" name="Width" column="`width`" />
<property type="string" length="50" name="Datatype" column="`datatype`" />
<property type="int" not-null="true" name="FieldNo" column="`field_no`" />
<property type="int" name="FieldOrder" column="`field_order`" />
<property type="int" name="StdId" column="`Std_Id`" />
<many-to-one name="StreamId" cascade="none" column="`stream_Id`" />
<bag name="FkFieldMappingsStreamFields" inverse="true" lazy="false" cascade="all">
<key column="`field_no`" />
<one-to-many class="DataTransfer.FieldMappings,DataTransfer"/>
</bag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="DataTransfer.FieldMappings,DataTransfer" table="`Field_Mappings`" lazy="false">
<id name="MappingId" column="`Mapping_Id`" type="int">
<generator class="native" />
</id>
<property type="int" name="StdFldId" column="`Std_fld_Id`" />
<property type="int" name="FieldPosition" column="`Field_Position`" />
<many-to-one name="FieldNo" cascade="none" column="`Field_No`" not-null="true" property-ref="FieldNo" />
</class>
</hibernate-mapping>
To make things easy on myself, there is one record in stream_fields and the field_no value is 1 and 20 is the value in StrFldId.
SELECT fkfieldmap0_.[field_no] as field5_1_, fkfieldmap0_.[Mapping_Id] as Mapping1_1_, fkfieldmap0_.[Mapping_Id] as Mapping1_3_0_, fkfieldmap0_.[Std_fld_Id] as Std2_3_0_, fkfieldmap0_.[Field_Position] as Field3_3_0_, fkfieldmap0_.[Field_No] as Field4_3_0_ FROM [Field_Mappings] fkfieldmap0_ WHERE fkfieldmap0_.[field_no]=#p0; #p0 = '20' –