NHibernate: Violated not-null constraint when saving HasMany relation with Cascade=AllDeleteOrphan - nhibernate

My bean looks like this:
public class A {
...
[HasMany (MapType = typeof(B), Table = "B_table", ColumnKey = "A_object_id",
Fetch = FetchEnum.Join,
RelationType = RelationType.List, Index = "id",
Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
IList<B> BList { get; set; }
...
}
and when perform Save on this bean I expect that beans of type B will be automatically
saved (and deleted on update) too. NHibernate surely is trying that, but it does so
with B_table.A_object_id set to NULL first and then NHibernate updates B_table setting the proper B_table.A_object_id value (that is: A.ID).
This is not what I want, as I have a NOT NULL constraint in the database.
My question is: how to make NHibernate automatically save the child objects with the proper ID set from the start? I know I can create A bean, save it, get it's brand new ID, create B beans, set their A_object_id and then save B beans... but it's a workaround.

Unidirectional relationships (in which only the parent knows about the child) always result in an update for setting the Id. I'm not sure why and it doesn't make a lot of sense to me either but that's just how NHibernate works.
You need to create a bidirectional relationship where the HasMany would have an Inverse = true and B would have a reference to class A in it (which should be populated when you add B to the A collection.

Related

Any way in Grails 3 for hasOne to do a lazy fetch?

Grails 3.2.5. Is see from my sql dump that the hasOne relationship does an eager fetch. This used to be the case back in prior versions of Grails, and the behavior could not be overridden. Is this still the case? What is the recommended model for a 1:1 relationship where we want a lazy fetch on the dependent object?
A little background. My "Comment" object has a one-to-one relationship with a "CommentText" object, where the text object holds Oracle clob text - some of it large. I only wanted to get the text when explicitly required to do so. The fk was in the dependent database text object, hence the "hasOne". Fortunately I was able to move the fk to the owner side of the association via an embedded domain object and update the db schema.
Throughout, I was unable to get lazy loading of the hasOne dependent object. Tried fetch: 'lazy'; fetchMode: 'lazy, and other variations of things. I needed a full domain class association because of "find" actions that needed to traverse the association.
I would still prefer the hasOne approach, if loading were indeed lazy.
Old question, but I just encountered the same problem so I'll answer for later reference.
Basically, it is impossible to lazy-fetch a hasOne property in Grails 3 (tested with 3.3.11, assuming Hibernate). But there are some workarounds.
The immediate lazy-fetch N+1 problem
As soon as you put hasOne: [child: Child] on the parent class, GORM will force you to make the relationship into a bidirectional one-to-one, and it will put the foreign key on the child table.
When you then fetch entities of the parent, it will immediately fetch all of the child entities as well, but it will do a query for every child (N+1 problem).
So a statement like this
Parent.list(max: 10)
will issue one query to get the 10 parents, and then do a query where parent_id = ? for each of the 10 children.
Even if you put fetch: 'lazy' and batchSize: 10 on the mapping of the child in Parent.groovy, the behavior is the same.
Workaround 1: One-directional with FK on the parent table
This is the solution you mention in your post. If you don't need to access the parent from the child side, you can make the relationship one-directional, which will put the FK on the parent table.
Now when fetching the Parent entity it will fetch the child_id from the parent table automatically, but keep the child property as a Hibernate proxy.
The child entity behind the proxy will correctly only be fetched once you access it. The batchSize mapping seems to be ignored here though, so when you actually start accessing the .child entities it will again issue one query per Parent.
Workaround 2: One-to-many and just access the first element
If you really want to keep the FK on the child table and also have lazy loading, you can use this hackaround.
On the Parent.groovy you could specify it like this
static hasMany = [children: Child]
static transients = ['child']
Child getChild() {
children ? children.first() : null
}
static mapping = {
children batchSize: 100
}
Now when you fetch the Parent entities it will correctly ignore the child property, and if you e.g. loop through a list of Parent and access the .child on each, it will only issue one single query for batchSize Parents.
So code like this:
def parents = Parent.list(max: 10)
parents.each {
log.info it.child.subProperty
}
will do the first query to get the 10 parents, and then one single query to lazily batch-fetch the children for up to batchSize parents. With Sql logging enabled it should look something like this:
select child0_.parent_id, child0_.id, ... from child child0_ where child0_.parent_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Workaround 3: The eager-fetch non-workaround
If your application code almost always uses the child property, then one option is to give up on lazy fetching and just specify child fetch: 'join' in Parent.groovy.
This will eliminate the N+1 lazy fetching problem, but as a downside Hibernate will now LEFT JOIN the child table and select all it's properties every time you request the Parent entity, even if you never touch the child property.
Workaround 4: Replace hasOne with mapping column: 'id'
class Face {
Nose nose // due to mapping, column 'nose_id' is not required
static mapping = {
nose column: 'id', insertable: false, updateable: false
}
}
class Nose {
static belongsTo = [face: Face] // due to mapping, column 'face_id' is not required
static mapping = {
// use the parent object's ID as this ID
// optional, but clean
id generator: 'foreign', params: [property: 'face']
// reference to the parent object so we don't need an extra column
face column: 'id', insertable: false, updateable: false
}
}

Fluent NHibernate: foreign key field not being set in unidirectional association

I have a database with a ProbateCases table and a Properties table. The Properties table has a foreign key to the ProbateCases table called ProbateCaseId, so the relationship between ProbateCases and Properties is one-to-many.
My domain layer has a ProbateCase class and a Property class. The ProbateCase class has a collection of Properties defined as follows:
private IList<Property> _properties = new List<Property>();
public virtual IEnumerable<Property> Properties { get { return _properties; } }
public virtual Property AddProperty()
{
Property property = new Property();
_properties.Add(property);
return property;
}
The corresponding part of the Fluent NHibernate mapping looks like this:
HasMany(x => x.Properties).Where("Deleted = 0").KeyColumn("ProbateCaseId").Cascade.All().Access.CamelCaseField(Prefix.Underscore);
Note that the association is unidirectional - the ProbateCase class has a collection of Properties, but the Property class does not have a ProbateCase member.
I'm finding that querying works fine - NHibernate is creating the appropriate SQL to get Properties with the appropriate ProbateCaseId value.
However, when I save a ProbateCase to which I have added a new Property, the INSERT SQL does NOT contain a value for the foreign key field - so I get a SQL Exception complaining of a NULL value in the foreign key:
INSERT INTO AdminOverview.Properties (PropertyName) VALUES ('Name of property') -- Where the hell is the ProbateCaseId field value???
Should I be expecting NHibernate to populate the foreign key value itself, or is there something else I should be doing?
From http://nhibernate.info/doc/nh/en/index.html#collections-onetomany:
Very Important Note: 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". See the discussion of bidirectional associations later in this chapter.

NHibernate Parent/Child relation

I ve got the following setup:
public class ParentEntity
{
public ICollection<ChildEntity> {get; set; }
}
public class ChildEntity
{
// do i need to the parent here?
}
I managed to save the ParentEntity and cascaded the save to the added child entities which were saved as well. But in the db table the ParentId reference of the child was set to allow NULL. When setting it to NOT NULL the save failes since the ParentId in the child table is NULL.
What's happening there? ;)
When
You should map both sides of the relationship normally, and when you add a child to the parent's collection, you should also set the parent property on the child. Normally you would achieve this by writing a method like this:
public void AddChild(ChildEntity child)
{
this.Children.Add(child);
child.Parent = this;
}
NHibernate persists the ParentId column in the Child table based on the mapped property in the ChildEntity class. The definition of the one-to-many relationship merely allows NHibernate to load the collection from the database based on values in this column
I am having the same issue and need this to either have nHibernate expose the foreign key column, or do it in class via collection.
Problem: nHibernate creates the collection object (IList, for example) and you can not override the or listen to the add events of basic collections.
This becomes an issue only because it is required by the WCF RIA Services framework.

nHibernate one-to-many inserts but doesnt update

Instead of getting into code, I have a simple question. Default behavior for a simple one-to-many is that it inserts the child record then updates the foreign key column with the parent key.
Has anyone ever had a one-to-many where the child object gets inserted but not updated resulting in a row in my table with a null in the foreign key column?
I want the default behaviour for a standard one-to-many. I don't want to have to add the parent as a property to the child.
Thanks.
This would happen if you didn't have cascade="save-update" on your set/bag
or if you set your session's FlushMode to 'None' or 'Commit'
and saved the child using your childRepository and neglected to save the object containing the collection using its repository.
I think you have to set parent reference in child item.
class Parent {
public virtual IList<Child> Children;
}
class Child {
public virtual Parent Parent;
}
Parent p = new Parent();
Child c = new Child();
c.Parent = p;
p.Children = new List<Child>();
p.Children.Add(c);
Now when you save this transient object p you will have the right foreign key in the child table.

NHibernate - What kind of association(s) is this?

I'm having some trouble getting NH to persist my object graph.
I have (something like) this:
/*Tables*/
TABLE Parent
ParentID PK
LastEventID NULL
TABLE Event
EventID PK
ParentID FK NOT NULL
//Model Classes
public class Parent
{
public List<Event> Events; //Inverse
//Denormalized bit
public Event LastEvent; //not inverse
}
public class Event
{
public Parent Parent; //Makes the association up there Inverse
}
I'm creating a new Parent, creating a new Event, adding the new Event
to Parent.Events and setting Parent.LastEvent to the new Event.
When I tell NH to save the Parent I get an error about a transient
object needing to be saved first. I assume its because the association
between Parent and Event is not clear.
The way the SQL needs to go is to insert the Parent with a null
LastEvent, then insert the Event, then update Parent.LastEvent.
So how do I get NH to do this?
Without seeing your mapping schema, I'll have to guess.
Are you cascading your updates? From the reference:
To save or update all objects in a graph of associated objects, you must either
Save(), SaveOrUpdate() or Update() each individual object OR
map associated objects using cascade="all" or cascade="save-update".
Assuming you don't already have this, does adding cascade="all" or cascade="save-update" to the side marked inverse="true" fix the problem?