NHibernate Save Is Trying to Clear Child KeyColumn Id On Update - nhibernate

I am trying to create a parent object that has multiple children in a 1 to many relationship. I am not referencing the Parent object on the child object, instead I am mapping its keycolumn as a field.
When I try to save this object for the first time, it works as expected without any issues (cascading all the Id's and children). When I try to get the object from the database, update some properties on it and re-save it again, it fails. The actual error message I am getting is "Could not delete collection".
The error above is due to the fact that it is trying to set the "ParentId" field on the child objects to NULL (which violates the FK constraint I have in the db). If I remove this constraint from the DB, the end result is what I want; however, I do not want it to perform this update (setting parent id to null) at all and I'm not sure why it is. From what I can tell in the SQL code it is generating and sending to the DB, everything else appears to be correct and it would all work if it wasnt for that last update statement.
Obviously, I must have something wrong with my mapping but I cannot figure out what. I tried adding Not.KeyUpdate() but that simply made it not generate a key at all. Does anyone have any ideas what I am doing wrong??
Thanks in advance, I really appreciate it!!!
Please see below for my mapping:
public class Parent
{
public Parent()
{
Children = new List<Child>();
}
public virtual Guid Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate().Not.LazyLoad();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId);
}
}

This is caused by the fact, that the Collection of children is not marked as inverse="true".
What you can do is: I. to remove the constraint from DB. NHiberante simply must (without the inverse setting) do 2 steps. Firstly update record to break relation, secondly (due to cascades) alse delete the item
II. Change the mapping, and entities. Like this:
A Child must have reference to the Parent:
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Mapping then will be like this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children)
.KeyColumn("ParentId")
.Cascade.SaveUpdate()
.Not.LazyLoad()
.Inverse() // here we do have the inverse setting
;
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId).Not.Insert().Not.Update(); // this is readonly now
References(x => x.Parent).Column("ParentId"); // just a Parent is writable
}
}
Now, you have to all the time properly set the relation in the C# code. I.e. if child is added to Parents collection, it should also get set the Parent reference
parent.Children.Add(child);
child.Parent = parent;
NHibernate will now issue only one statement, to delete child from its table

Related

NHibernate - Unable to save child entities

This is supposed to be a simple 1-N relationship, however I am not able to save the children as NHibernate uses null as a value in the insert statement of the child.
public class Event
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<EventParameter> Parameters{ get; set; }
public Event() { Parameters = new List<EventParameter>(); }
}
[Serializable]
public class EventParameter : Parameter
{
public virtual Event Event { get; set; }
public virtual string ComparisonMethod { get; set; }
public override bool Equals(object obj) {}
public override int GetHashCode() {}
}
The mappings are like this
public EventMapping()
{
Table(...);
Id(x => x.Id)
.Column(...)
.GeneratedBy
.Custom<global::NHibernate.Id.SequenceGenerator>(builder => builder.AddParam("sequence", "..."));
Map(x => x.Name).Column("Name").Length(100);
HasMany<EventParameter>(x => x.Parameters)
.KeyColumns.Add(...)
.Inverse()
.Cascade.All();
}
public EventParameterMapping()
{
Table(....);
CompositeId()
.KeyProperty(x => x.Event.Id, "...")
.KeyProperty(x => x.ParameterId, "...");
References(x => x.Event);
}
The Insert statement for the parent is correct, however for the child it is not.
INSERT INTO ...
(...columns...)
VALUES (..., null, ...)
Before this happens I get the following warning: Unable to determine if entity is transient or detached; querying the database. Use explicit Save() or Update() in session to prevent this.
I do use Save() in the transaction. Any ideas why this happens?
Solution here is suprisingly simply. We have to use inverse="true" mapping:
HasMany<EventParameter>(x => x.Children)
.KeyColumns.Add("...")
.Inverse()
.Cascade.All();
But, this is kind of optimization, which would require both sides of relation to be always properly set in our C# code:
var parent = ...;
var child = new Child();
// both sides MUST be set
parent.Children.Add(child);
child.Parent = parent;
Please, observe this for more details
Inverse = “true” example and explanation

NHibernate - Self referencing mapping interferes with foreign key mapping

I have the following classes:
public class Track
{
public virtual int Id { get; set; }
public virtual Track MainMix { get; set; }
public virtual IEnumerable<Track> SubMixes { get; set; }
public virtual IList<FileVersion> Files { get; set; }
}
public class FileVersion
{
public virtual int Id { get; set; }
public virtual Track Track { get; set; }
}
And the following mappings:
public class TrackMap : ClassMap<Track>
{
public TrackMap()
{
Id(x=>x.Id);
References(x => x.MainMix);
HasMany(x => x.SubMixes)
.Inverse()
.Cascade.All()
.KeyColumn("MainMix_id");
HasMany(a => a.Files)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.All();
}
}
public class FileVersionMap : ClassMap<FileVersion>
{
public FileVersionMap()
{
Id(x => x.Id);
References(x => x.Track);
}
}
There is omitted code for the sake of simplicity. The Track table has a "MainMix_id" column that is a self referencing column for a parent/child relationship among Track records.
When I try to fetch a track from the database the NHProfiler tells me that Nhibernate tries to fetch the fileversions of that track with the following query:
SELECT files0_.MainMix_id as MainMix9_1_,
files0_.Id as Id1_,
files0_.Id as Id9_0_,
files0_.Track_id as Track8_9_0_
FROM [FileVersion] files0_
WHERE files0_.MainMix_id = 3 /* #p0 */
It seems like it has confused the parent id column of the Track table with its primary key column. When I remove References(x => x.MainMix) from the Track mapping the query is correct, but I don't have the parent track record returned.
Let me know if I can clarify this any more and thanks in advance for your help!
Does this make a difference?
TrackMap :
References(x => x.MainMix).Column("MainMix_id");
FileVersionMap :
References(x => x.Track).Column("Track_id");

Having trouble deleting the child objects in a one-many relationship using NHibernate

The title pretty much sums up the issue that I am facing. Basically I have 2 classes:
public class Parent : IIntegerIdentifiable
{
public virtual long Id { get; set; }
public virtual ICollection<Child> Children {get; set; }
}
public class Child : IIntegerIdentifiable
{
public virtual long Id { get; set; }
public virtual Parent Parent { get; set; }
}
I'm defining a one-to-many relationship between these classes.The class maps for both of them are:
public sealed class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id).GeneratedBy.HiLo(
"HiLoUniqueKey", "NextHi", "99", "ObjectType = 'Parent'");
HasMany(x => x.Children).KeyColumn("Parent_Id").Inverse().Cascade.AllDeleteOrphan();
}
}
public sealed class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Id(x => x.Id).GeneratedBy.HiLo("HiLoUniqueKey", "NextHi", "99", "ObjectType = 'Child'");
References(x => x.Event).Cascade.All();
}
}
Since I wanted to delete All the children for a particular parent from the database, I wrote the following function in ChildRepository:
public void ClearChildEntries(long parentId)
{
//I had tried the following and was getting errors before I read the other posts on the topic
//QueryOver().Where(x => x.Event.Id == eventId).List().ToList().ForEach(Remove);
//Session.flush()
//Current code
var parent = _parentRepository.GetById(parentId);
parent.Children.Clear();
_parentRepository.SaveOrUpdate(parent);
}
Our code is setup so that the flush gets called at end of request. Whenever I try executing the code, I get the error:
Cannot insert the value NULL into column 'parent_id', table 'test.dbo.Child'; column does not allow nulls. UPDATE fails.
I also tried first removing each of the child entities and flushing the session and then updating the parent collection. It gives the same error. The question is
a) Why is nHibernate trying to update the parent id column to null and then delete?
b) What can I do to get the deletes working?
Just so that you know I'm not wasting your time, I have referred other posts on the topic, like
- How to delete child object in NHibernate?
- http://ayende.com/blog/1890/nhibernate-cascades-the-different-between-all-all-delete-orphans-and-save-update
but nothing seems to work. Can someone please point me in the right direction? Thank you for your help!!
I think it has to do something with the Cascade option try changing the Cascade to All_Delete_Orphan From Cascade.All
http://ayende.com/blog/1890/nhibernate-cascades-the-different-between-all-all-delete-orphans-and-save-update

Fluent nHIbernated - HasMany relationship in same table

I'm creating a model for a website top-menu structure -
I have a MenuObject model:
public class MenuObject
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual List<MenuObject> Children { get; set; }
}
and a mapping:
public mapMenu()
{
Id(x => x.Id)
.Not.Nullable();
Map(x => x.Title)
.Not.Nullable();
HasMany<MenuObject>(x => x.Children)
.AsList();
}
Basically i want to be able to create a "Top Level" Menu item then add some child items to it - in database terms, there should be a ParentId field that contains the ID of the parent Menu Item (if any - this could be null)
I'm struggling to get my head around how this should be defined in my object model. Also once i hav this configured, how would i go about saving children? Would it be something like
public void InsertChild(MenuObject parent, MenuObject child)
{
parent.Children.add(child)
session.SAve(parent)
.....
}
or would I have to save the child independantly and link it to the parent explicitly?
Edit *****
Thanks - so now i have the following in my model:
public virtual MenuObject Parent { get; set; }
public virtual List<MenuObject> Children { get; set; }
and this in the mapping:
HasMany(x => x.Children)
.AsList()
.Inverse()
.Cascade.All()
.KeyColumn("ParentId");
References(x => x.Parent, "ParentId");
I can now add children to parent itms in the following way:
oChild.Parent = oParent;
session.SaveOrUpdate(oParent);
session.Save(oChild);
transaction.Commit();
I think i'm onto a winner! Is that the best way to do it? THanks gdoron
or would i have to save the child independantly and link it to the parent explicitly?
Anyway you have to "link' it to it's parent, If don't want to get exceptions...
You will have to save the child independently only if you don't specify Cascade.Save\All() in the mapping:
HasMany<MenuObject>(x => x.Children)
.AsList().Inverse.Cascade.All(); // Inverse will increase performance.
You have to add a Parent property to connect the "child" to it's "Parent".
It's mapping is:
References(x => x.Parent);
P.S.
You don't have to write Not.Nullable on Id, It's the defaults.

Fluent NHibernate Many to One Mapping Problem

Edit
I have the answer and will post it up tomorrow.
I have a class that appears to be using the parent primary key column instead of it's own primary key column.
There are two classes involved here ActionHistory and ActionData. The parent class, ActionHistory, represents an action and details who performed the action. The child class, ActionData, represents a piece of data that was created by the action. Each ActionHistroy has a number of ActionData and this is represented through a many to one mapping.
The classes are:
public class ActionHistory
{
public virtual int Code { get; set; }
public virtual string Doneby { get; set; }
public virtual IList<ActionData> _ActionData{ get; set;}
}
public class ActionData
{
public virtual int Code { get; set; }
public virtual double Data{ get; set; }
public virtual ActionHistory _ActionHistory{ get; set; }
}
My mappings are:
public class ActionHistoryClassMap : ClassMap<ActionHistory>
{
public ActionHistoryClassMap ()
{
Table("ACTIONHISTORY");
Id(x => x.Code, "CODE").GeneratedBy.Assigned();
Map(x => x.Doneby, "DONEBY");
HasMany(x => x._Actiondata).KeyColumn("CODE").AsBag().Cascade.All();
}
}
public class ActionDataClassMap : ClassMap<ActionData>
{
public ActionDataClassMap ()
{
Table("ACTIONDATA");
Id(x => x.Code, "CODE").GeneratedBy.Assigned();
Map(x => x.Data, "DATA");
References(x => x._ActionHistory, "ACTIONHISTORYID");
}
}
The problem is when an ActionHistory class is queried the correct ActionHistory is returned but the HistoryData that it contains is wrong. A list of HistoryData should be returned but only one HistoryData is returned and it's Code value is the Code values of the parent ActionHistory class. I believe my problem stems form the fact that both classes have the same primary key column name and I'm not handling this properly. The columns that that are related are CODE from the ACTIONHISTORY table and ACTIONHISTORYID from the ACTIONDATA table. The database is fixed so I can't rename any columns. In this case data is only read from the database and not saved or updated.
I downloaded NHibernate Profiler to help me with this problem and have found the exact point in the SQL where the problem occurs.
This is the SQL query that is generated to return the ActionData (where 18 is the ActionHistory primary key):
SELECT actiondat0_.CODE as CODE1_,
actiondat0_.CODE as CODE9_0_,
actiondat0_.DATA as DATA9_0_,
actiondat0_.ACTIONHISTORYID as ACTIONHI4_9_0_
FROM ACTIONDATA actiondat0_
WHERE actiondat0_.CODE = 18 /* #p0 */
The last line should be:
WHERE actiondat0_.ACTIONHISTORYID = 18 /* #p0 */
But I just do not understand why the mapping is not generating the right query.
I know it was something blindingly obvious and after hours of getting nowhere it of course jumps out at me.
The ActionHistory class map should of course be:
public class ActionHistoryClassMap : ClassMap<ActionHistory>
{
public ActionHistoryClassMap ()
{
Table("ACTIONHISTORY");
Id(x => x.Code, "CODE").GeneratedBy.Assigned();
Map(x => x.Doneby, "DONEBY");
HasMany(x => x._Actiondata).KeyColumn("ACTIONHISTORYID")
.AsBag().Cascade.All();
}
}
The difference being the KeyColumn method is now ACTIONHISTORYID as opposed to CODE. I didn't cop that KeyColumn should reference the ActionHistory key column. It seems obvious now and it didn't help that all other times I implemented it in this project the columns with the relationship always had the same name.