Fluent NHibernate HasMany not updating the FK - nhibernate

I'm using latest Fluent NHibernate lib (0.1.0.452) and I have a problem with saving child entitites.
I think this is rather common scenario... I've got a parent with mapping:
HasMany<Packet>(x => x.Packets)
.Cascade.All()
.KeyColumnNames.Add("OrderId");
and a simple Packet class that (in a domain model and FNH mapping) doesn't have any reference to the parent.
What gets generated is a correct Packets table that contains a column named OrderId.
What doesn't work is the saving.
Whenever I try to save parent object, the children are also saved, but the FK stays untouched.
I checked the SQL and in INSERT statement the OrderId doesn't even appear!
INSERT INTO KolporterOrders (CargoDescription, SendDate, [more cols omitted] ) VALUES ('order no. 49', '2009-04-22 00:57:44', [more values omitted])
SELECT LAST_INSERT_ID()
INSERT INTO Packets (Weight, Width, Height, Depth) VALUES ('To5Kg', 1, 1, 1)
SELECT LAST_INSERT_ID()
As you see the OrderId is completely missing in the last INSERT.
I also checked the generated NH mapping and it seems it's ok:
<bag name="Packets" cascade="all">
<key column="OrderId" />
<one-to-many class="Company.Product.Core.Packet, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
I tried setting Cascade to different values. I even added References to the PacketMap (FNH mapping class).
Any ideas why the OrderId is not being inserted?
Edit: forgot to mention: I'm using MySQL5 if it matters.
Edit2: The above FNH mapping generates hbm with bag (not a set) - I edited it.
The C# code used for saving:
var order = new Order();
NHSession.Current.SaveOrUpdate(order); //yes, order.Packets.Count == 1 here
///Order.cs, Order ctor
public Order()
{
CreateDate = DateTime.Now;
OrderState = KolporterOrderState.New;
Packets = new List<Packet>();
Packets.Add(new Packet()
{
Depth = 1,
Height = 1,
Width = 1,
Weight = PacketWeight.To5Kg
});
}
the session gets flushed and closed at EndRequest.

Ok, my fault. I was testing it in ApplicationStart of global.asax, so the Request hadn't been created so the session wasn't flushed. I realised it when I tested it on a simple ConsoleApp project when I saw that flushing actualy causes the FK col update.
Anyway: thanks for help!

In a "vanilla" parent-children object model, you must update the child's object's reference to the parent in order to cause NHibernate to update the child record's reference to the parent.
In an "inverted" parent-children object model, you must modify the parent's collection of children objects in order to cause NHibernate to update the child records' references to the parent.
It seems you may want to be using an "inverted" parent-children object model.
In the XML mapping, you need
<set name="Packets" cascade="all" inverse="true">
<key column="OrderId" />
<one-to-many class="Company.Product.Core.Packet, Core,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>
In the Fluent mapping, you need
HasMany<Packet>(x => x.Packets)
.Cascade.All()
.Inverse()
.KeyColumnNames.Add("OrderId")
;

This is really strange. You should check subsequent Updates, NHibernate sometimes updates foreign keys afterwards, and then it doesn't appear in the insert.
Make sure that OrderId does not have several meanings on the Packets table. To check this, change the name of OrderId to something else.
Cascading has nothing to do with it. It only controls if you need to save the child explicitly.

Related

Many-to-many NHibernate mapping to a legacy app view

My many-to-many relationship does not involve the standard "joining-table" approach, in which a table stores the "FK1-to-FK2" relationships.
Instead, I'm "loosely" joining to a legacy read-only view as follows:
Appointment class (based on the Appointment table)
int AppointmentId (PK)
int OrderId
List<LegacyOrder> LegacyOrders
LegacyOrder class (based on the LEGACY_ORDERS_VIEW view in our legacy system)
int OrderId (composite PK)
int VersionNumber (composite PK)
An Appointment can have many (versions of a) LegacyOrder.
A LegacyOrder can have many Appointments, but this relationship is not important in our application.
I want to populate the LegacyOrders property with all LegacyOrders for the specified OrderId.
My attempt at mapping is as follows:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="OrderId" />
<many-to-many class="LegacyOrder" column="ORDER_ID" />
</bag>
</class>
....but I'm getting "could not execute query" exceptions due to invalid SQL.
I think the table referred to in the <bag> mapping should be the "joining table".... but I don't have one.
I'm fairly sure my mapping approach is fundamentally wrong.... what's the right way to go about it?
Edit:
Thanks Radim: perhaps a better name for LegacyOrder would be LegacyOrderVersion: each record in that view corresponds to a "version" of an order, rather than an order.
i.e. An order may be for 100 units, then when say 20 units are collected, another record is written with the same OrderId but for 80 units. (I did warn you it was legacy :)
If an Appointment (in the new system) can retrieve all related LegacyOrderVersions, then it can derive useful properties such as CurrentLegacyOrderVersion and OriginalLegacyOrderVersion.
FWIW: this works great for me:
<class name="Appointment" table="Appointments" lazy="true">
<bag name="Orders" inverse="true">
<key property-ref="OrderId" column="ORDER_ID" />
<one-to-many class="LegacyOrder" />
</bag>
</class>
One way how to solve this a bit challenging DB structure, could be with the property-ref feature. See more details here: 5.1.10. many-to-one, working even for our many-to-many scenario.
So firstly we have to map the property, which we will use as a reference:
<class name="Appointment" table="Appointments" lazy="true">
...
// the column name is coming from the Appointment table
<property name="OrderId" column="ORDER_ID" />
So, now we have mapped the OrderId - the property (column) - which we will use to map the <bag>.
Well, honestly, now I am not sure what your thoughts were. In case that LegacyOrder would have one column mapped as key (the Order_ID) we can do it like this.
<bag name="Orders" table="LEGACY_ORDERS_VIEW" inverse="true">
<key column="ORDER_ID" property-ref="OrderId" />
<many-to-many class="LegacyOrder" formula="ORDER_ID" />
</bag>
But that's not reasonable, because the Order_Id is not unique. In fact the LegacyOrder view, does not seem to be the entity at all. It could be some real intermediate structure.
I would say, that what the pairing view Legacy_orders_view represents, is the map (dictionary) saying: The Order with ID == X, had these Versions.
This information, the int Version numbers, is the only thing/information I can find out as really interesting. The OrderId is representing still the same Order
Anyhow, with the proeprty-ref and more detailed knowledge what you need to achieve we can at the end have:
// I. Map
public virtual IDictionary<int, Order> OrderMap { get; set; }
above the Version will play the role of the Key, the Order is questinable, because it will be the same Order as the OrderId says
// II. Version collection
public virtual IList<int> OrderVersions { get; set; }
in this case we will get set of int numbers related to the OrderId. Seems to be the only interesting message we can get.
III. There must be more information, about your entity/DB model. Why does the Legacy_orders_view exists at all? What would we like to get from that "relation" at the end?

How to query a foreign key column with NHibernate, without retrieving the related entity

Say I have two classes: Parent and Child. A Parent has a property Children, which is of course a collection of Child objects.
Child doesn't have a ParentId property. It does have a Parent property.
So, my NHibernate mapping for Child includes:
<many-to-one name="Parent" class="Parent" column="ParentId" cascade="save-update" />
And my Parent mapping includes:
<bag name="children" access="field" inverse="true" cascade="all-delete-orphan">
<key column="ParentId" />
<one-to-many class="Child" />
</bag>
Now here's what I want to do: I want to get all the Child objects with a certain ParentId. I know I can first get the Parent and then return its Children property. But what if I'd want to query the Child table directly?
If it would be a mapped property (for example, Name), I could use NHibernate's criteria, but in this case, ParentId isn't mapped.
I tried using something like:
criteria.Add(Restrictions.Eq("Parent.Id", 1));
But that doesn't work. I resorted to using SQLCriterion (as explained here), but a friend/colleague got me thinking there must be a better way.
Any ideas? Something with projections and Restrictions.EqProperty?
You have to alias the association path. This will return a proxy for Parent assuming that are using lazy loads. You can access the parent's Id property without triggering a load.
return _session.CreateCriteria<Child>()
.CreateAlias("Parent", "parent")
.Add(Restrictions.Eq("parent.Id", parentId))
.List<Child>();
I've done this using query over. Here is an example:
Child foundChild =
session.QueryOver<Child>()
.Where(x => x.Parent.Id == 1234).SingleOrDefault<Child>();
I think it can be done via Criteria like this:
criteria.Add(Restrictions.Eq("Parent", Session.Load<Parent>(1));

Add/delete item to bag collection

I am working with nHibernate, and trying make sense of bag collections. My data structure is relatively straight-forward...
Entry:
<class name="Entry">
<id name="id" column="EntryId">
<generator type="guid.comb"/>
</id>
<property name="Name" column="Name"/>
<bag name="Results" table="Results" cascade="all">
<key column="EntryId" />
<one-to-many class="Result"/>
</bag>
</class>
Result:
<class name="Result">
<id name="id" column="ResultId">
<generator type="guid.comb"/>
</id>
<property name="Score" column="Score" />
<many-to-one name="Entry" class="Entry" cascade="all" />
</class>
What I would like to do, which doesn't seem to be working, is the following:
Entry entry = new Entry();
entry.Name = "Name";
// have tried saving at this point to:
// dbSession.SaveOrUpdate(entry);
Result result = new Result();
result.Score = 100;
entry.Results.Add(result);
dbSession.SaveOrUpdate(entry);
It seems to be creating the entry record in the database, but not the result record. In my database, I have EntryId as a foreign key in the Result table. Similarly, I would like to be able to remove the result object from the collection, and have it persist to the database. I thought the cascade feature took care of this, but not sure what I have done wrong...
EDIT
I now have it adding the result object into the database, but delete does not seem to work:
Entry entry = Entry.Load(id);
entry.Results.Remove(result);
dbSession.SaveOrUpdate(entry);
I have tried adding cascade="all-delete-orphan", but this seems to remove both parent and children. I just want it to delete the one entry object from the database??
In the end, this came down to my hbm file mappings not being correct.
Entry.hbm.xml
<bag name="Results" table="Result" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="EntryId"/>
<one-to-many class="Result"/>
</bag>
Result.hbm.xml
<many-to-one name="Entry" class="Entry" column="EntryId"/>
I originally had cascade="all-delete-orphan" on the many-to-one mapping, which was not correct. What happened was that all children and the parent record was being deleted.
I can now add and remove with the following:
Result r = new Result();
Entry entry = new Entry();
// AddResult method sets the Entry object of the Result
// result.Entry = this;
entry.AddResult(r);
session.SaveOrUpdate(entry);
To delete:
entry.Results.Remove(result);
session.SaveOrUpdate(entry);
To add to a collection you need to explicitly save the child object when it is added. Ditto when you delete an object from a collection.
So you would do:
entry.Results.Add(result);
session.Save(result);
session.Save(entry);
session.Flush();
The foreign key also has to be nullable. The reason why you have to do this is NHibernate has to save the child first with no association to the parent. Then when the parent is saved the foreign key column on the child gets updated with the parent's Id, creating the relation. This is because NHibernate may not have the needed parent id key value until the second operation (parent is saved) has completed.
I guess you have this part figured out.
Delete works the same way for different reasons - remove the child from the parent collection, then delete the child explicitly, then update the parent:
entry.Results.Remove(result);
session.Delete(result);
session.Update(entry);
session.Flush();
You removed result from the collection and updated the entry. That only tells Nhibernate to delete the relationship between the entry and the result - you never actually deleted the result object itself.
I notice that, in your collection, you have defined the FK column as:
<key column="EntryId" />
But you are not overriding the column in your many-to-one, which means you have two different columns (Entry and EntryId) for the same relationship.
This might be it or not... but it doesn't hurt to check :-)
if you are using Mapping by Code then use both Cascade.All and Cascade.DeleteOrphans options. unlike the xml mapping, there is no single option for "all-delete-orphan" in Mapping by Code.
Bag(x => x.Results, c =>
{
c.Key(k =>
{
k.Column("EntryId");
});
c.Cascade(Cascade.All | Cascade.DeleteOrphans);
}, r => r.OneToMany())

NHibernate Parent/Child Orphaned Records with Web Service

I have a web service that accepts an Invoice, which contains LineItem children. It then updates the database to either create or update the Invoice using NHibernate.
When an invoice is updated, it is passed to the web service along with all LineItem children it now has. Adds and updates work perfectly. However, if a child LineItem is deleted from a previously persisted Invoice by the Web Service consumer and re-submitted, that LineItem is not actually removed from the database, but rather it's back reference to the parent is set to NULL. I am using (trying to use) cascade="all-delete-orphan" without success.
I suspect that the problem might be due to the stateless nature of the operation (I don't first have the LineItem in Invoice.LineItemList on the web service side and then delete it, but rather just get a list of LineItem's as they now should be). However, NHibernate IS smart enough to null the back-reference column, so I hope there's a straightforward way to get it to delete that row instead.
Here are the mappings (simplified).
Parent object (Invoice):
<property name="InvoiceNumber" />
<!-- If inverse="true", InvoiceId is NOT set to NULL and the record remains -->
<bag name="LineItemList" table="lineitems" inverse="false" cascade="all-delete-orphan">
<key column="InvoiceId"/>
<one-to-many
class="LineItem"/>
</bag>
Child Objects (LineItems):
<many-to-one lazy="false" name="Parent" column="InvoiceID" not-null="false"
class="Invoice,Company.Business"
/>
<property name="LineItemNumber" />
<property name="SalesAmount"/>
The Web Service persistence code looks like this:
[WebMethod]
public Invoice PutInvoice(Invoice invoice)
{
// Necessary to rebuild parent references, see Blog
foreach (LineItem item in invoice.LineItems)
{
item.Parent = invoice;
}
using (PersistenceManager pm = new PersistenceManager())
{
pm.Save<Invoice>(invoice);
}
return invoice; // Return version potentially modified with DB-assigned ID
}
You are right this has to to with the detached state of your objects and is a known limitation in admission to performance which NHibernate describes as the not implemented feature of 'persistence of reachability'. However you could of course easily delete all LineItems without valid invoice reference but i also don't like this solution.
Usually i use client objects to achieve statelessness which of course results in loading the invoice before manipulating.

nhibernate mapping many-to-many: why was the whole bag collections deleted and reinserted?

Nhibernate users, professionals, gurus and developers are expected. Please help !!!
I want to realise a n:m relation between two classes. A student attends in more courses and a course consists of more students as members. I do a bidirectional association many-to-many with bag to get the both lists from each site.
The two Student and Course classes:
public class Student {
// Attributes........
[XmlIgnore]
public virtual IList MyCourses { get; set; }
// Add Method
public virtual void AddCourse(Course c)
{
if (MyCourses == null)
MyCourses = new List<Course>();
if (!MyCourses.Contains(c))
MyCourses.Add(c);
if (c.Members== null)
c.Members= new List<Student>();
if (!c.Members.Contains(this))
c.Members.Add(this);
}
public virtual void RemoveCourse(Course c)
{
if (MyCourses != null)
MyCourses.Remove(c);
if (c.Members!= null)
c.Members.Remove(this);
}
}
public class Course {
// Attributes........
[XmlIgnore]
public virtual IList Members { get; set; }
}
In database there are two tables t_Student, t_Course and a relation table tr_StudentCourse(id, student_id, course_id).
<class name="Student" table="t_Student" polymorphism="explicit">
.....
<bag name="MyCourses" table="tr_StudentCourse">
<key column="student_id" />
<many-to-many class="Course" column="course_id" not-found="ignore" />
</bag>
</class>
<class name="Course" table="t_Course" polymorphism="explicit">
.....
<bag name="Members" table="tr_StudentCourse" inverse="true">
<key column="course_id" />
<many-to-many class="Student" column="student_id" not-found="ignore" />
</bag>
</class>
Course was chosen as inverse in the bidirectional association. I did the same as example (Categorie, Item) in section 6.8 of nhibernate documentation. So I saved the student object after inserting a course in the list MyCourses by calling the Add/Remove-method.
Student st1 = new Student();
Course c1 = new Course();
Course c2 = new Course();
st1.AddCourse(c1);
st1.AddCourse(c2);
session.saveOrUpdate(st1);
That works fine, the st1, c1 and their relation (st1,c1) can be find in the database. The relation datasets are (id=1, st1.id, c1.id) and (id=2, st1.id, c2.id).
Then I add more courses to the object st1.
Course c3 = new Course();
st1.AddCourse(c3);
session.saveOrUpdate(st1);
I can see the 3 relation datasets, but the two old relations were deleted and new three were created with another new id. (id=3, st1.id, c1.id), (id=4, st1.id, c2.id) and (id=5, st1.id, c3.id). There are not dataset with id=1 and 2 more in relation table.
The same by deleting if I remove a course from student.MyCourse and then save the student object. All collection was also deleted and recreated a new list which less one deleted element. That problem makes the id in the relation table increates very fast and a have troble by doing a backup of relation.
I have looked some days in internet, documentation and forums to find out why the whole old collection was deleted and a new as created by each changing, but I was not successful. It is a bug from nhibernate mapping or did I do any wrong?
I am very very grateful to your help and answer.
Nhibernate documentation http://nhforge.org/doc/nh/en/index.htm
NHibernate can't create, delete or
update rows individually, because
there is no key that may be used to
identify an individual row.
By note from "6.2. Mapping a Collection"
As soon as you have id in tr_StudentCourse you can try using indexed collections, i.e. replace <bag> with <map> or similar and add <index> element to the mapping:
<index
column="id"
type="int"
/>
or even create a special entity for the relation table to use with <index-many-to-many>.
This is what I've found on the NHibernate website:
Hibernate is deleting my entire
collection and recreating it instead
of updating the table.
This generally happens when NHibernate
can't figure out which items changed
in the collection. Common causes are:
replacing a persistent collection
entirely with a new collection
instance passing NHibernate a manually
constructed object and calling Update
on it.
serializing/deserializing a
persistent collection apparently also
causes this problem.
updating a
with inverse="false" - in this case,
NHibernate can't construct SQL to
update an individual collection item.
Thus, to avoid the problem:
pass the same collection instance that
you got from NHibernate back to it
(not necessarily in the same session),
try using some other collection
instead of <bag> (<idbag> or <set>),
or try using inverse="true" attribute
for <bag>.