One-to-one or many-to-one mapping using primary key of each table - nhibernate

I've got a legacy schema with a main table and secondary table, where the secondary table connects to the main table by having the same primary key (a Secondary doesn't not necessarily exist for a given Main). I've been searching up-and-down for XML mappings that will make this work but haven't found anything that works for me.
<class name="Secondary" table="Secondary" lazy="true" dynamic-insert="true" dynamic-update="true">
<id name="mainId" type="Int32">
<column name="MAIN_ID" not-null="true" />
<generator class="foreign">
<param name="property">Main</param>
</generator>
</id>
<one-to-one class="Main" name="Main" constrained="true" />
</class>
<class name="Main" table="Main" lazy="true" dynamic-insert="true" dynamic-update="true">
<one-to-one name="Secondary" cascade="all-delete-orphan" class="Secondary" />
Also tried on the Main side, still no go. It doesn't necessarily break, but it certainly doesn't do what I expect. For example:
session.Query<Main>().Count(m => m.Secondary != null) generates
select
cast(count(*) as INT) as col_0_0_
from
MAIN main0_
where
main0_.MAIN_ID is not null
Note that it's using the MAIN_ID from the MAIN table and is ignoring Secondary altogether.

This issue is fixed in NHibernate 5.3
It's a known issue (PR with suggested fix is here).
For now as a workaround in LINQ you can call Count on some non-ID and not-nullable property:
session.Query<Main>().Count(m => m.Secondary.NotNullableProperty != null)
If such property doesn't exist (or you just want to easily find all such hacky usages in future) you can additionally map your Id column as read-only property and use it instead:
<property name="ForceJoinId" not-null="true" column="MAIN_ID" insert="false" update="false" />
session.Query<Main>().Count(m => m.Secondary.ForceJoinId != null)

Related

Polymorfic many-to-one NHibernate Mapping

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

One-to-Many List Not Being Saved with NHibernate

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.

NHibernate exception while deleting object graph: not-null property references a null or transient value

I've got a scheme (fields aren't necessary):
a busy cat http://picsearch.ru/share/image-BCE8_4E168F3B.jpg
I've got mappings:
Entity
<class name="LogicalModel.Entity" table="`Entity`" lazy="true">
<id name="Id" ..> ... </id>
<bag name="Attributes" lazy="true" cascade="all-delete-orphan" fetch="select" batch-size="1" access="property" inverse="true">
<key column="`Entity`" />
<one-to-many class="LogicalModel.Attribute" />
</bag>
<bag name="Keys" lazy="true" cascade="all-delete-orphan" fetch="select" batch-size="1" access="property" inverse="true">
<key column="`Entity`" />
<one-to-many class="LogicalModel.Key" />
</bag>
</class>
Attribute
<class name="LogicalModel.Attribute" table="`Attribute`" lazy="true">
<id name="Id" ..> ... </id>
<many-to-one name="Type" class="LogicalModel.Entity" column="`Type`" cascade="save-update" fetch="select" not-null="true" foreign-key="fk_TypeAttribute" />
<many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityAttributes" />
</class>
Key
<class name="LogicalModel.Key" table="`Key`" lazy="true">
<id name="Id" ..> ... </id>
<bag name="KeyAttributes" lazy="true" cascade="all-delete-orphan" fetch="select" access="property" inverse="true">
<key column="`Key`" />
<one-to-many class="LogicalModel.KeyAttribute" />
</bag>
<many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityKeys" />
</class>
KeyAttribute:
<class name="LogicalModel.KeyAttribute" table="`KeyAttribute`" lazy="false">
<id name="Id" ..> ... </id>
<many-to-one name="Attribute" class="LogicalModel.Attribute" column="`Attribute`" cascade="save-update" fetch="select" not-null="true" foreign-key="fk_AttributeKeyAttribute" />
<many-to-one name="Key" class="LogicalModel.Key" column="`Key`" cascade="none" fetch="select" not-null="true" foreign-key="fk_KeyKeyAttributes" />
</class>
Now please take a look...
As you see, We've got one-way master association KeyAttribute - Attribute, so it's just many-to-one and I don't need back association at all.
Now the problem is when I'm trying to delete whole graph - delete Entity object (notice: Entity actually aren't loaded at all, it's just set of proxies, that's why NHibernate make additional SELECT queries to check references before delete)
like this
Session.Delete(Entity); // here PropertyValueException:
// not-null property references a null or transient value: LogicalModel.KeyAttribute.Attribute
Session.Flush(); // Actually I use transactions in my code, but don't mind
SQL Profiler:
exec sp_executesql N'SELECT entities0_.[Id] as Id1_1_, entities0_.[Id] as Id1_45_0_,
FROM [Entity] entities0_ WHERE entities0_.[LogicalModel]=#p0',N'#p0 uniqueidentifier',#p0='DC8F8460-9C41-438A-8334-97D0A94E2528'
exec sp_executesql N'SELECT attributes0_.[Entity] as Entity12_1_, attributes0_.[Id] as Id1_1_, attributes0_.[Id] as Id1_16_0_, attributes0_.[Type] as Type11_16_0_, attributes0_.[Entity] as Entity12_16_0_
FROM [Attribute] attributes0_ WHERE attributes0_.[Entity]=#p0',N'#p0 uniqueidentifier',#p0='63E4D568-EAB2-4DF2-8FED-014C8CB2DE22'
exec sp_executesql N'SELECT keys0_.[Entity] as Entity4_1_, keys0_.[Id] as Id1_1_, keys0_.[Id] as Id1_43_0_, keys0_.[Entity] as Entity4_43_0_
FROM [Key] keys0_ WHERE keys0_.[Entity]=#p0',N'#p0 uniqueidentifier',#p0='63E4D568-EAB2-4DF2-8FED-014C8CB2DE22'
exec sp_executesql N'SELECT keyattribu0_.[Key] as Key4_1_, keyattribu0_.[Id] as Id1_1_, keyattribu0_.[Id] as Id1_0_0_, keyattribu0_.[Attribute] as Attribute3_0_0_, keyattribu0_.[Key] as Key4_0_0_
FROM [KeyAttribute] keyattribu0_ WHERE keyattribu0_.[Key]=#p0',N'#p0 uniqueidentifier',#p0='103D8FB3-0B17-4F51-8AEF-9623616AE282'
So what we can see:
not-null property references a null or transient value: LogicalModel.KeyAttribute.Attribute
happened just after NH check field Attribute (not-null constraint in db, it's ok) in class KeyAttribute (see profiler log).
It's pretty fun, cause NH have to delete Attributes and KeyAttributes both, NH read information about Attribute field in KeyAttribute class, FOUND it in DB, NOT FOUND it in NH session (!!!) (cause Attributes was loaded before), and just throw this stupid error.
What I've already tried to do:
1. make not-null="false". In this case NH makes additional update - try to set Attribute=NULL - cause constraint violation in DB.
2. set lazy="false", lazy="no-proxy" on many-to-one association for KeyAttribute-Attribute - nothing;
Now I don't like the idea of interceptors because there are to many scenarios where I've got the same situation, I need common solution
Please, guys, any suggestions?
In my opinion it may be caused by your lazy load on all entities of model.
When deleting entity, it loads and delete referenced Attribute list, loads referenced Key list, loads referenced KeyAttribute list (to have key of deletion) and then it falls in not-null property references a null or transient value because referenced Attribute has been deleted before in session.
You can check that by removing all lazy load in your mapping files.
A quick solution may be to keep lazy load but to force a full load of model (with hibernate initialize()) when deleting, for example in a Delete(Entity) static method in Entity factory.
Have you tried setting on-delete="cascade" in
<class name="LogicalModel.Key" table="`Key`" lazy="true">
<id name="Id" ..> ... </id>
<bag name="KeyAttributes" lazy="true" cascade="all-delete-orphan" fetch="select" access="property" inverse="true">
<key column="`Key`" on-delete="cascade" />
<one-to-many class="LogicalModel.KeyAttribute" />
</bag>
<many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityKeys" />
Because in profile you will see nh trying to update something to null which is non nullable
NH sometimes requires to set references to null. Usually this is to avoid problems in models where circular references exist. But it is not always clever enough to find a way to avoid it, even if the is one.
So it may require to allow nulls in some foreign key fields, of course not only in the mapping file, also in the database. It actually should solve the problem.
Alternatively, you could also delete the data table by table using HQL. This works fine in all cases where you don't have inheritance and if you know all entities and the order to delete them:
object entityId;
// gets keys to delete
List<object> keyIds = Session
.CreateQuery("select id from Key where Entity = :entity")
.SetEntity("entity", Entity)
.List<object>();
// delete KeyAttribute which reference the key
Session.CreateQuery("delete KeyAttribute where Key.id in (:keyIds)")
.SetParameterList("keyIds", keyIds)
.ExecuteUpdate();
// delete the keys
Session.CreateQuery("delete Key where id in (:keyIds)")
.SetParameterList("keyIds", keyIds)
.ExecuteUpdate();
// get attributes to delete
List<object> attributeIds = Session
.CreateQuery("select id from Attribute where Entity = :entity")
.SetEntity("entity", Entity)
.List<object>();
// delete KeyAttributes which reference the attributes
Session.CreateQuery("delete KeyAttribute where Attribute.id in (:attributeIds)")
.SetParameterList("attributeIds", attributeIds )
.ExecuteUpdate();
// delete the attributes
Session.CreateQuery("delete Attribute where id in (:attributeIds)")
.SetParameterList("attributeIds", attributeIds )
.ExecuteUpdate();
Session.CreateQuery("delete Entity where id = :entityId")
.SetParameter("entityId", Entity.Id)
.ExecuteUpdate();
Note:
You may break the parameter lists into piece if they exceed the size of around 2000 (in SQL Server).
The session gets out of synch when deleting directly in the database. This doesn't cause any problems when deleting is all you do. When you are doing other staff in the same session, clear the session after deleting.

Bidirectional one to many (or many to one) cascade delete behaviour. It works, but why?

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.

NHibernate: Where clause on one-to-many relationships doesn't work when column name is ambiguous

It is possible to specify an arbitrary SQL where clause for collection mappings. For example:
<map name="myEntity" where="foo = 1" />
However if the column name is ambiguous for some reason, the sql fails. For example, this can occur if you are trying to use joins for example.
Given that the table aliases are automatically generated, you can't qualify the column name. This makes the feature seem rather silly. Does anyone know if there is a work around?
NHibernate should figure out the correct alias for the property you are referencing. Is foo a mapped property of the item entity type (the item type that is in the map collection) ?
For example this works:
<class name="Category" table="Category">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Name" not-null="true" length="255" />
<bag name="ProductList" table="Product" cascade="none" where="Name like '%test%'" fetch="join">
<key column="CategoryId" />
<one-to-many class="Product" />
</bag>
</class>
There is a property on both Category and the Product class named "Name" but nhibernate will in this case use the on defined on the Product class.