I would love to thank #Stefan Steinegger and #David helped me out yesterday with many-to-many mapping.
I have 3 tables which are "News", "Tags" and "News_Tags" with Many-To-Many relationship and the "News_Tags" is the link table.
If I delete one of the news records, the following mappings will delete all my news records which have the same tags. One thing I need to notice, I only allowed unique tag stored in the "Tag" table.
This mapping make sense for me, it will delete the tag and related News records, but how can I implement a tagging system with NHibernate?
Can anyone give me some suggestion? Many thanks.
Daoming.
News Mapping:
<class name="New" table="News" lazy="false">
<id name="NewID">
<generator class="identity" />
</id>
<property name="Title" type="String"></property>
<property name="Description" type="String"></property>
<set name="TagsList" table="New_Tags" lazy="false" inverse="true" cascade="all">
<key column="NewID" />
<many-to-many class="Tag" column="TagID" />
</set>
</class>
Tag Mapping:
<class name="Tag" table="Tags" lazy="false">
<id name="TagID">
<generator class="identity" />
</id>
<property name="TagName" type="String"></property>
<property name="DateCreated" type="DateTime"></property>
<!--inverse="true" has been defined in the "News mapping"-->
<set name="NewsList" table="New_Tags" lazy="false" cascade="all">
<key column="TagID" />
<many-to-many class="New" column="NewID" />
</set>
</class>
When I run into trouble like that, the first thing I twiddle with is the cascade option.
As far as I know, the mapping is correct (I'm using mapping files that look exactly the same). The problem is the cascade attribute: the "all" option forces NHibernate to propagate each action on an entity to the instances of the collection. In your case, when you delete a news item all related tags are deleted too.
You probably should use "none" (in that case you'll eventually end up with some unused tags in the database) or "delete-orphans" (on the news item side - use "none" on the tag side).
Use the cascade option "save-update".
The option "all" will cascade deletes, which you do not want in this case. But you the option "none" will require that the Tag entity is already persisted which I guess might not always be the case.
So by setting the cascade to "save-update" new Tags till be inserted in the Tags table and in the link table News_Tags, but when you remove a tag from a News entity it will only be removed from the link table not the Tags table.
Related
I'm trying to map the following classes:
PessoaFisica and PessoaJuridica inherits Pessoa.
Cliente has an association with Pessoa, it may be PessoaJuridica or PessoaFisica.
When I save a Cliente object with PessoaFisica, for example, thats ok. But when I try to update and I set the property Pessoa from Cliente to PessoaJuridica and try to update, it updates, but it generates a new row in table TB_PESSOA and the old row, in PessoaFisica is not deleted. It creates a new row to PessoaJuridica, but the old row remains. What's wrong with my mapping XMLs ? Why NHibernate does not delete the old row before insert the new polymorphic object ?
Those are the mapping files I am using
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="SALClassLib.Masterdata.Model" assembly="SALClassLib">
<class name="Pessoa" table="TB_PESSOA">
<id name="Id">
<column name="ID_PESSOA" not-null="true"/>
<generator class="increment" />
</id>
(other properties...)
<joined-subclass name="PessoaFisica" table="TB_PESSOA_FISICA">
<key column="ID_PESSOA" />
(other properties...)
</joined-subclass>
<joined-subclass name="PessoaJuridica" table="TB_PESSOA_JURIDICA">
<key column="ID_PESSOA" />
(other properties...)
</joined-subclass>
</class>
<class name="Cliente" table="TB_CLIENTE">
<id name="Id">
<column name="ID_CLIENTE" not-null="true"/>
<generator class="increment" />
</id>
<many-to-one name="Pessoa" class="Pessoa" cascade="all" column="ID_PESSOA" not-null="true" unique="true" />
Thank you
NHibernate cascading is nicely explained here: NHibernate Cascades: the different between all, all-delete-orphans and save-update
One of the option, is cascade="all-delete-orphan" which could be seen as what you are asking for.
BUT
Cascading deletion of the orphans is correct only in parent-child scenario (no parent ==> no children) or one-to-one mapping. (i.e not vice versa child-parent)
In your case, you do ask for deletion of the referenced object. But NHibernate (well no-one) can know, if it is not referenced by some other "child".
If you need to delete previous Person assigned, you can always do it in code - but explicitly
I'm very new to NHibernate, and I'm running into a problem while saving a list of child objects.
NOTE
<class name="Note" table="NOTE">
<id name="NoteID" column="NOTE_ID">
<generator class="identity" />
</id>
...
<list name="Sections" table="NOTE_SECTIONS" cascade="all" lazy="false">
<key column="NOTE_ID"/>
<index column="SORT_ORDER"/>
<one-to-many class="Section"/>
</list>
</class>
NOTE SECTION
<class name="Section" table="NOTE_SECTIONS">
<id name="SectionID" column="Section_ID">
<generator class="identity" />
</id>
<property name="NoteID" column="NOTE_ID"/>
...
</class>
The mappings work perfectly for reading the data. However, when I make a change to the Note Section, The queries it generates appears to be going through the proper steps, but then I get the following error:
NHibernate.Exceptions.GenericADOException: could not delete collection: [Domain.Note.Sections#1][SQL: UPDATE NOTE_SECTIONS SET NOTE_ID = null, SORT_ORDER = null WHERE NOTE_ID = #p0] ---> System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'NOTE_ID', table 'NOTE_SECTIONS'; column does not allow nulls. UPDATE fails.
I have read that in order to save like this it will need to be bidirectional. But I've also read that bidrectional mappings don't work with Lists. It's important that my collection have a maintained order - what's the best way to save?
You should use inverse="true" on your collection mapping if you wish to save child objects in this manner.
<list name="Sections" table="NOTE_SECTIONS" inverse="true" cascade="all" lazy="false">
<key column="NOTE_ID"/>
<index column="SORT_ORDER"/>
<one-to-many class="Section"/>
</list>
Inverse Attribute in NHibernate
probably you would need to refer Note from Note Section as many-to-one relation.
I have two Nhibernate mappings for two classes, Category and Product. My Category class has two properties that are collections. The Children property is a collection of type Category which represents child categories (represents a category menu, typical parent child scenario). The second property on the Category class is a Products collection which represents all the products under a category.
What I am trying achieve is when I delete a category I want the category to deleted but not the product. So I want the product to be orphaned. i.e have its foreign key (CategoryId) in the Product table set to null. I don't want to delete a product just because I have deleted a category. I want to be able to reassign in at a later time to another category. My mappings representing the mentioned scenario are below.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
<class name="Category">
<id name="Id">
<generator class="hilo" />
</id>
<version name="Version"/>
<property name="Name" not-null="true" unique="true" />
<set name="Products"
cascade="save-update"
inverse="true"
access="field.camelcase-underscore">
<key column="CategoryId" foreign-key="fk_Category_Product" />
<one-to-many class="Product" />
</set>
<many-to-one name="Parent" class="Category" column="ParentId" />
<set name="Children"
collection-type="naakud.domain.Mappings.Collections.TreeCategoriesCollectionType, naakud.domain"
cascade="all-delete-orphan"
inverse="true"
access="field.camelcase-underscore">
<key column="ParentId" foreign-key="fk_Category_ParentCategory" />
<one-to-many class="Category"/>
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
<class name="Product">
<id name="Id">
<generator class="hilo" />
</id>
<version name="Version" />
<property name="Name" not-null="true" unique="true" />
<property name="Description" not-null="true" />
<property name="UnitPrice" not-null="true" type="Currency" />
<many-to-one name="Category" column="CategoryId" />
</class>
</hibernate-mapping>
With this mapping, when I delete a category which has products associated with it I get the following constraint error.
The DELETE statement conflicted with the REFERENCE constraint "fk_Category_Product". The conflict occurred in database "naakud", table "dbo.Product", column 'CategoryId'.
The statement has been terminated.
However, when I remove the inverse=true attribute on the Products collection in the Category mapping then it works fine. My CategoryId foreign key in the products table is set to null and thus disassociating a product with a category. Which is what I want.
I have read about the inverse attribute and I understand it signifies the owning side of a relationship and updates/inserts/deletes are done in a different order which is why I think it solves my problem. So my question is, am I solving my problem in the correct way? How does this affect performance? (not much I suspect). Would it be better to have a uni-directional relationship without the many to one side and have the inverse attribute set to true to get better performance? Or am I going crazy and completely missing the point?
Another way of fixing the delete problem is by setting the many-to-one property to null on all the related entities to null before flushing.
I can think of at least two ways to do it:
In the same method that calls session.Delete(category), do:
foreach (var product in category.Products)
product.Category = null;
Using HQL:
session.CreateQuery(
"update Product set Category = null where Category = :category")
.SetParameter("category", category)
.ExecuteUpdate();
Update:
Here's a proof-of-concept implementation using an event listener.
I assume that you read about Inverse Attribute in NHibernate
As the error message says, your DELETE generates a conflict with the foreign key constraint, meaning that the DB cannot delete the Category as long as there are Products referencing that particular Category.
What you could do (if you can alter the DB schema) is applying "ON DELETE SET NULL" to your foreign key constraint. That way, when the DELETE is executed, the DB will automatically set all references in the Product table to NULL.
If you cannot modify the foreign key, then you would have little choice but to remove the inverse attribute. Doing so will result in NHibernate first setting the Product.Category reference to NULL and then deleting the Category.
If you need Product.Category fairly often then you should not get rid of the many-to-one attribute in Product.
Regarding the performance, that depends on how often you insert Products. Each insert will result in an additional update to set the foreign key. That should not be a problem, though.
Hey all, I'm kicking the tires on NHibernate and have a conoundrum I have been scratching my head over for a bit now, working with a legacy database with some fairly complex relationships.
ClaimRoot has a primary key of a claimGUID.
ClaimRoot has a bag of Claimdetails associated by claimGUID (this works a treat).
The problem is that ClaimRoot also has an optional one to one relationship with ClaimFinancials (not all ClaimRoots have ClaimFinancials, but most do). But the PK for ClaimFinancials is a FormID field. This field exists in the ClaimRoot, but is not the PK.
I've posted a mapping below with extra columns removed to protect the innocent.
<class name="ClaimRoot" table="tbl_ClaimRoot" schema="DB1.dbo">
<id name="ClaimGUID">
<generator class="guid"/>
</id>
<property name="FormID" />
<property name="LastFormNoteText" />
<bag name="ClaimDetails" inverse="true">
<key column="ClaimGUID"/>
<one-to-many class="ClaimDetails"/>
</bag>
</class>
<class name="ClaimDetails" table="tbl_ClaimDetails" schema="DB2.dbo">
<id name="RowID">
<generator class="native"/>
</id>
<property name="ClaimGUID" />
<property name="SeqNo"/>
<property name="B1A_InsID" />
<many-to-one name="Root" column="ClaimGUID" foreign-key="ClaimGUID"/>
</class>
<class name="ClaimFinancials" table="tbl_ClaimFinancials" schema="DB1.dbo">
<id name="FormID">
<generator class="native"/>
</id>
<property name="CreatedDate"/>
<property name="SubmittedDate" />
</class>
Thanks in advance!
-Bob
Assuming the FormID is use only for linking ClaimRoot and ClaimFinancials, it sounds like you want a many-to-one relationship from ClaimRoot to ClaimFinancials. Replace the FormId property on ClaimRoot with a many-to-one.
<class name="ClaimRoot" table="tbl_ClaimRoot" schema="DB1.dbo">
...
<many-to-one name="ClaimFinancials" column="FormID" />
...
</class>
A many-to-one relationship can be be used even if there is only 'one' on the 'many' side. If you were generating a schema, you can specify unique="true" to generate the constraint in the database. With a legacy database, that won't matter.
I have two classes: Family and Address.
A family has a physical address and a mailing address.
The mapping file for Family looks like:
....
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<many-to-one name="PhysicalAddress" class="Address" column="PhysicalAddressId" cascade="all" unique="true" />
<many-to-one name="MailingAddress" class="Address" column="MailingAddressId" cascade="all" unique="true" />
...
The mapping file for Address looks like:
...
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="StreetAddress1" column="StreetAddress1" />
<property name="StreetAddress2" column="StreetAddress2"/>
<property name="City" column="City" />
<property name="State" column="State" />
<property name="ZipCode" column="ZipCode" />
...
(Note that Family-PhysicalAddress and Family-MailingAddress are one-to-one relationships.)
What I would like to happen is that when I execute
aFamily.MailingAddress = null;
session.Save(aFamily);
session.Flush();
I expect NHibernate to automatically delete the mailing address record from SQL Server for me.
BUT, that doesn't happen. NHibernate does not delete the address record from SQL Server.
Is there any way I can make it work?
Thank you!
This behaviour isn't supported by NHibernate. Of course the problem is that you probably don't have access to the NHibernate session in your domain logic where the change is made.
One possible -- though admittedly not ideal solution -- is to simply run another process to clean up orphaned entities.
Here is a discussion of this scenario:
http://colinjack.blogspot.com/2008/03/nhibernate-gotchas-orphans-and-one-to.html
And a link to a ticket on the issue:
https://nhibernate.jira.com/browse/NH-1262
Unfortunately NHibernate currently does not support automatic deletions of orhphans for many-to-one (Hibernate v3 in Java does support it). It is only supported on lists (cascade="all-delete-orphan").
What you can try to do is to use component mapping. Maybe it is possible to embed many-to-one into a component.
But I think it would better to explicitly delete the related object.