Nhibernate save update delete relations - nhibernate

I have a product that has 1 or more product relations.
Entities: Product and ProductRelation
So product has a property List(Of ProductRelation)
Now I have a checkboxlist where I can select a number of products that I want to assign to this product.
When I add a new collection of ProductRelations with the new products, It should delete all old relations and save the new one. But this does not work. It does not delete the old one and also not saves the new one.
I have used the following hbm.xml
<bag name="RelatedProduct" inverse="true" lazy="true" cascade="all">
<key column="FromID" />
<one-to-many class="Kiwa.Objects.RelatedProduct,Kiwa.Objects" />
</bag>

Your hbm file is not visible. :)
But, why do you add a new collection ?
This is the reason why things are going wrong.
You should clear the collection (remove the items from the collection), and just add the new items to the collection, without replacing the collection itself.

You should NEVER replace mapped collection once it has been persisted. NHibernate needs that specific collection instance (which is created / injected by NHibernate during entity load) to track deletes.
You should instead delete / update / replace individual elements (e.g. RelatedProduct instances) within existing collection. If you truly want to delete all the previously saved RelatedProducts and insert new ones (why?), you can clear the RelatedProduct List - but don't replace it with a new List instance.

Related

ORM issue after upgrading to ColdFusion 2018 from version 11

We migrated from ColdFusion 11 to ColdFusion 2018 and now ORM is breaking the EntitySave method and we are getting below error message.
Error Message - The root cause of this exception was: coldfusion.orm.hibernate.HibernateSessionException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1.
We have 2 classes with one-to-many relationship like Email.cfc (parent) and EmailItems.cfc (child). When we try to save Email.cfc object the hibernate creates an UPDATE query for EmailItems as well and this is happening with ColdFusion 2018 only.
Here is the defined property in Email.cfc.
<cfproperty name="EmailItems"
lazy="true"
fieldtype="one-to-many"
inverse="true"
fkcolumn="EmailID"
cfc="EmailItem"
singularName="EmailItem"
type="struct"
structkeycolumn="EmailItemKey"
structkeytype="string"
cascade="none"
/>
We are using inverse and it still create an UPDATE query for child class 'EmailItems' which is the reason for failing EntitySave(Email). Everything works fine with ColdFusion 11.
Finally, I found the fix for this. It seems the inverse="true" properties doesn't work the same WITH cf2018 as it is working with CF2011.
After doing some research found that, by default a cascade property is added in <cfproperty> tag for one-to-many relationship whose value is UPDATE that means for any orphaned child object it is trying to update while this object doesn't exist at database level.
So, when adding cascade="save-update" in my <cfproperty> tag for on-to-many relationship, it fixes the problem because now it tries to INSERT instead of UPDATE for any orphaned child object.

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>.

Map a property to the latest entry in NHibernate

Let's say my domain looks like this:
I have an object, Vehicle, that has an OdometerReading property.
An OdometerReading has the Miles & Date (when it was read).
I need to keep a history of all OdometerReadings for the Vehicle in the database, but don't want the entire odometer history to belong to the Vehicle object. What I would like is for the OdometerReading property map to the most recent OdometerReading entry out of the database.
I thought about mapping the whole collection of OdometerReadings to the Vehicle, and having a dynamic property called CurrentOdometerReading that would order them and return the latest one, but I don't need the whole collection under the Vehicle in my domain, and it seems like I would be getting more data out of the database than I need.
Is that possible with NHibernate? How would I map such a thing?
There are a few ways of doing this depending on what you want your domain model to look like. My preferred choice is to use a custom collection type, for example IOdometerReadingCollection, which you can add extra methods to. In this case it might be something like:
public interface IOdometerReadingCollection : IList<OdometerReading>
{
OdometerReading Latest { get; }
}
This way you can do:
OdometerReading reading = vehicle.OdometerReadings.Latest;
which I prefer to:
OdometerReading reading = vehicle.LatestOdometerReading;
There's lots of documentation about custom collections, which you can find with a simple google search.
If this approach isn't for you there are other options. You may be able to use a property with a formula (I'm not sure if that works with complex types?), or a regular NHibernate association where you'd have the key of the latest OdometerReading on your Vehicle mapping. As you also mentioned you could just load all the OdometerReadings, which depending on your use case might actually be fine.
I hope this helps, or at least points you in the right direction.
There is a "where" clause that you can put in your collection mapping. Check the reference documentation.
I would map the OdometerReading property as a component, then use a named query to ensure it's populated with the latest reading out of the database. (In this example, you'd have a sql-query with a name of "vehicle" that does the SQL to load the Vehicle columns along with the latest Odometer reading)
<class name="Vehicle">
<property name="Type" not-null="true"/>
<component name="OdometerReading">
<property name="Miles" />
<property name="Date" />
</component>
<loader query-ref="vehicle"/>
</class>

Fluent NHibernate HasMany not updating the FK

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.