cascade="all" or cascade="save-update"? NHibernate one-to-many won't update - nhibernate

Problem encountered
When I create a transient instance with a children collection, everything gets persisted.
Aside, if I update an instance of one of the children object, it doesn't get updated when I save the parent object.
I'm actually using cascade="all"
Problem reproduction
The problem occurs when I have loaded all of my Customer occurences, and I change an invoice, though I always use the same ISession.
var repository = new CustomerRepository(session);
var customers = repository.GetAll();
var customer = customers.Where(c => c.Name == "Stack Overflow").FirstOrDefault();
customer.Invoices
.Where(i => i.Number == "1234")
.Approve(WindowsIdentity.GetCurrent().Name);
repository.Save(customer);
Step by step debugging clearly shows the repository.Save() method being executed, and the changes won't show inte the underlying database.
An update directly against the database table is possible, so no contraint causing the update to fail on the database-side.
Herewith some code in case it might help.
Customer.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="Customer" table="AC_CUST" schema="AC">
<id name="Id" column="AC_CUST_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">AC_CUST_ID_SEQ</param>
<param name="schema">AC</param>
</generator>
</id>
<property name="Name" column="AC_CUST_NAME" type="String"
not-null="true" />
<property name="PhoneNumber" column="AC_CUST_PHNUM" type="Int64"
not-null="true" />
<bag name="Invoices" table="ESO_RAPP_ACCES_INFO_DSQ" schema="AC"
fetch="join" lazy="true" inverse="true" cascade="all">
<key column="AC_CUST_ID" foreign-key="AC_CUST_INV_FK" />
<one-to-many class="Invoice" />
</bag>
</class>
</hibernate-mapping>
Customer
public class Customer {
public Customer() { Invoices = new List<Invoice>(); }
public virtual int Id { get; proected set; }
public virtual IList<Invoice> Invoices { get; protected set; }
public virtual string Name { get; set; }
public virtual string PhoneNumber { get; set; }
}
Invoice.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="Invoice" table="AC_INV" schema="AC">
<id name="Id" column="AC_INV_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">AC_INV_ID_SEQ</param>
<param name="schema">AC</param>
</generator>
</id>
<property name="Approved" column="AC_INV_APPRD" type="DateTime"
not-null="false" />
<property name="Approver" column="AC_INV_APPRR" type="String" length="15"
not-null="false" />
<property name="Number" column="AC_INV_NUMBR" type="String" length="15"
not-null="true" />
<property name="Produced" column="AC_INV_PROD" type="DateTime"
not-null="false" />
<many-to-one class="Customer" column="AC_CUST_ID" />
</class>
</hibernate-mapping>
Invoice
public class Invoice {
public Invoice() {
Items = new List<Item>();
Produced = DateTime.Now;
}
public virtual DateTime? Approved { get; protected set; }
public virtual string Approver { get; protected set; }
public virtual Customer Customer { get; set; }
public virtual int Id { get; proected set; }
public virtual string Number { get; set; }
public virtual DateTime? Produced { get; set; }
public virtual void Approve(string approver) {
Approved = DateTime.Now;
Approver = approver;
}
public virtual void Reject() { Produced = null; }
}
CustomerRepository
public class CustomerRepository {
public CustomerRepository(ISession session) { Session = session; }
public ISession Session { get; protected set; }
public Customer Save(Customer instance) {
Session.SaveOrUpdate(instance);
return Instance;
}
}
Related articles
nHibernate one-to-many inserts but doesnt update
NHibernate - code example for update
Any help appreciated.

Don't forget to Flush() your session!
NHibernate keeps track of all the changes along a session lifecycle which should only live for a form (Desktop) or a page (Web).
Because of the session is aware of all the changes, it doesn't necessarily commit the changes to the underlying database. Instead, it keeps a record of all the changes into a dictionary, then when it is flushed by calling ISession.Flush(), you actually demand the session to commit the changes for good.
So solution shall be:
repository.Save(customer);
session.Flush();
Or you may as well code a Commit() method within your repository which would Flush() the session upon a call.
repository.Save(customer);
repository.Commit();
And your repository would look like:
// Assuming you stock your session in the `Session` property.
public void Commit() { Session.Flush(); }
That's all!

Related

nhibernate weird DuplicateMappingException

I have two classes:
namespace fm.web
{
public class User
{
public static string default_username = "guest";
public static string default_password = "guest";
private UserType usertype;
public virtual int? Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual DateTime Datecreated { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
public virtual string Email { get; set; }
public virtual UserType Usertype
{
get { return usertype; }
set { usertype = value; }
}
}
}
namespace fm.web
{
public class UserType
{
public virtual int? Id { get; set; }
public virtual string Title { get; set; }
}
}
Here are the mapping files
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="User" table="[user]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Username" />
<property name="Password" />
<property name="Datecreated" />
<many-to-one name="Usertype"
class="UserType"
column="[type]"
cascade="all"
lazy="false"
/>
<property name="Firstname" />
<property name="Lastname" />
<property name="Email" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="UserType" table="[user_type]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Title" />
</class>
</hibernate-mapping>
I'm getting an exception: DuplicateMappingException
Could not compile the mapping document: fm.web.data.User.hbm.xml
Duplicate class/entity mapping User
Is nhibernate always this hard? Maybe I need a different framework.
I really think the mappings are fine which leads me to believe that the configuration setup is not quite right.
Please can you check that BuildSessionFactory is only called once on application start up.
Also please check that you are not including the mapping files twice as this will also throw this type of error.
Please post your configuration code.
You are correct in thinking that NHibernate is difficult to grasp for new comers espically the session management and mappings. Once you have grasped this then things get easier and are well worth the effort.

NHibernate mapping does not populate the bag

<class name="CashThreshold" table="CASH_THRESHOLD_COUNTERS" lazy="true" >
<id name="Id" column="ID" >
<generator class="assigned" />
</id>
<bag name="ThresholdNominalsList" cascade="all" inverse="true" lazy="false" table="CASH_THRESHOLD_CAS_COUNTERS">
<key column="CASH_THRESHOLD_ID" />
<one-to-many class="NominalThreshold" />
</bag>
Map second table
<class name="NominalThreshold" table="CASH_THRESHOLD_CAS_COUNTERS" lazy="true" >
<composite-id>
<key-property name="CashTrasholdId" column="CASH_THRESHOLD_ID" type="long"></key-property>
<key-property name="Nominal" column="NOMINAL" type="long"></key-property>
</composite-id>
<property name="MinNoteCount" column="MIN_NOTE_COUNT" />
<property name="MaxNoteCount" column="MAX_NOTE_COUNT" />
Table classes
public class CashThreshold : ICashThreshold
{
public virtual long Id { set; get; }
/// !!!!!!! IS ALWAYS AMPTY, but not null !!!!!
public virtual IList<INominalThreshold> ThresholdNominalsList { set; get; }
}
public class NominalThreshold : INominalThreshold
{
public virtual long CashTrasholdId { set; get; }
public virtual long Nominal { set; get; }
public virtual long MinNoteCount { set; get; }
public virtual long MaxNoteCount { set; get; }
public override bool Equals(Object obj)
{
var tmp = (INominalThreshold)obj;
return (tmp.CashTrasholdId == CashTrasholdId && tmp.Nominal == Nominal);
}
public override int GetHashCode()
{
return (int)CashTrasholdId ^ (int)Nominal;
}
}
Function for getting list of ICashThreshold
ICriteria selectAll = currentSession.CreateCriteria<ICashThreshold>();
IList<ICashThreshold> list = selectAll.List<ICashThreshold>();
Query executed whith no errors. Bag-query executed successfully in sql-client and returned 4 result, but IList< INominalThreshold > ThresholdNominalsList has no elements.
Thanks.
Problem solved. NHibernate mapped bag successfully, but list was empty, because the data in DB was NOT COMMITTED. I inserted test data in the table, but did not commit it. When I execute query in sql-client, it executed successfully(because do it in session, where table rows inserted), but hibernate had another session. Thats why NHibernate could not see the table data.

Saving child collections with NHibernate

I am in the process or learning NHibernate so bear with me.
I have an Order class and a Transaction class. Order has a one to many association with transaction. The transaction table in my database has a not null constraint on the OrderId foreign key.
Order class:
public class Order {
public virtual Guid Id { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual decimal Total { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
public Order() {
Transactions = new HashSet<Transaction>();
}
}
Order Mapping:
<class name="Order" table="Orders">
<cache usage="read-write"/>
<id name="Id">
<generator class="guid"/>
</id>
<property name="CreatedOn" type="datetime"/>
<property name="Total" type="decimal"/>
<set name="Transactions" table="Transactions" lazy="false" inverse="true">
<key column="OrderId"/>
<one-to-many class="Transaction"/>
</set>
Transaction Class:
public class Transaction {
public virtual Guid Id { get; set; }
public virtual DateTime ExecutedOn { get; set; }
public virtual bool Success { get; set; }
public virtual Order Order { get; set; }
}
Transaction Mapping:
<class name="Transaction" table="Transactions">
<cache usage="read-write"/>
<id name="Id" column="Id" type="Guid">
<generator class="guid"/>
</id>
<property name="ExecutedOn" type="datetime"/>
<property name="Success" type="bool"/>
<many-to-one name="Order" class="Order" column="OrderId" not-null="true"/>
Really I don't want a bidirectional association. There is no need for my transaction objects to reference their order object directly (I just need to access the transactions of an order). However, I had to add this so that Order.Transactions is persisted to the database:
Repository:
public void Update(Order entity)
{
using (ISession session = NHibernateHelper.OpenSession()) {
using (ITransaction transaction = session.BeginTransaction()) {
session.Update(entity);
foreach (var tx in entity.Transactions) {
tx.Order = entity;
session.SaveOrUpdate(tx);
}
transaction.Commit();
}
}
}
My problem is that this will then issue an update for every transaction on the order collection (regardless of whether it has changed or not).
What I was trying to get around was having to explicitly save the transaction before saving the order and instead just add the transactions to the order and then save the order:
public void Can_add_transaction_to_existing_order()
{
var orderRepo = new OrderRepository();
var order = orderRepo.GetById(new Guid("aa3b5d04-c5c8-4ad9-9b3e-9ce73e488a9f"));
Transaction tx = new Transaction();
tx.ExecutedOn = DateTime.Now;
tx.Success = true;
order.Transactions.Add(tx);
orderRepo.Update(order);
}
Although I have found quite a few articles covering the set up of a one-to-many association, most of these discuss retrieving of data and not persisting back.
Many thanks,
Ben
You need to set the cascade attribute on your mapping so that persistence is cascaded to the child objects:
<set name="Transactions" table="Transactions" lazy="false" inverse="true" cascade="all-delete-orphan">
Your Order object should have an AddTransaction method that sets the parent reference on the child. Something like:
public void AddTransaction(Transaction txn)
{
txn.Order = this;
Transactions.Add(txn);
}
This will cause the Transaction object to be persisted when the Order is persisted. You can expose the Order property on Transaction with the internal modifier so that it's not publicly visible.

NHibernate property mapping: columns and formula

When i map columns from the inspected table, i do this:
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace">
<column name="Name"/>
<column name="Name2"/>
</property>
How can I make property mapping initialize a UserType with data retrieved by the formula's sql query?
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace" formula="(...)"/>
fails with an exception "wrong number of columns".
Thanks in advance!
MyUserNameType should be a class level mapping so that you can map the result of the SQL function to a class. See these two posts for some possible help:
Class and SQL Function example: http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!478.entry
NHibernate Mapping with formula mapping example:
http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!477.entry
I'm the author of the articles referenced by Michael. I had no idea people where still interested and I'm not sure it's applicable with the latest NHibernate.
Here's a fresh link though: http://thoughtspam.wordpress.com/2007/12/19/nhibernate-property-with-formula/
example, using Northwind...
Mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Shipper, PropertyFormulaExample" table="Shippers" lazy="false" >
<id name="ShipperID" column="ShipperID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CompanyName" column="CompanyName" />
<property name="Phone" column="Phone" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Order, PropertyFormulaExample" table="Orders" lazy="false">
<id name="OrderID" column="OrderID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CustomerID" column="CustomerID" />
<property name="ShipVia" type="PropertyFormulaExample.Shipper, PropertyFormulaExample" formula="dbo.GetShipper(shipvia)" />
</class>
</hibernate-mapping>
Entities:
public class Order
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public Shipper ShipVia { get; set; }
}
public class Shipper : ILifecycle
{
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
#region ILifecycle Members
public LifecycleVeto OnDelete(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public void OnLoad(NHibernate.ISession s, object id)
{
}
public LifecycleVeto OnSave(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public LifecycleVeto OnUpdate(NHibernate.ISession s)
{
throw new NotImplementedException();
}
#endregion
}
And finally the SQL function:
CREATE FUNCTION dbo.GetShipper(#shipperId int)
RETURNS int
AS
BEGIN
RETURN #shipperId
END
Obviously, you’ll want the function to do something meaningful, but the idea is you return the PK for the entity and implement ILifecycle.

NHibernate - Lazy loading collections - not working

I've either misunderstood the NHibernate manual or I've done something wrong. Can anyone help?
I am trying to retrieve a User without the AuditLogEntrys.
But NHibernate is still loading the AuditLogEntrys. I only want the AuditLogEntrys loaded when I access the property.
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual IList<AuditLogEntry> AuditLogEntrys { get; set; }
}
public class AuditLogEntry
{
public virtual int Id { get; set; }
public virtual DateTime DateRead { get; set; }
public virtual string MachineName { get; set; }
}
Mappings:
<class name="Model.User, Model"
table="User"
lazy="true">
<id name="UserId" access="property" column="UserID">
<generator class="native"></generator>
</id>
<property name="UserName" access="property" />
<bag name="AuditLogEntrys" lazy="true" access="property">
<key column="UserID" />
<one-to-many class="Model.AuditLogEntry, Model"></one-to-many>
</bag>
<class name="Model.AuditLogEntry, Model"
table="AuditLog"
lazy="true">
<id name="Id" access="property" column="ID">
<generator class="native"></generator>
</id>
<property name="DateRead" access="property" column="DateRead"></property>
<property name="MachineName" access="property" column="MachineName"></property>
</class>
Code to get the user:
public IList<User> GetUserByUserName(string userName)
{
ICriteria criteria = NHibernateSession.CreateCriteria(typeof(User))
.Add(Expression.Eq("UserName", userName));
return GetByCriteria(criteria);
}
Now I'd expected a User object with an empty collection of AuditLogEntry's, but this is not what is happening.
Any ideas??
Thanks.
With lazy loading, you will get a populated list of objects, but they are not yet "hydrated" from the database. The lazily-loaded objects are not your entity types, but are instead "proxy objects" which will be populated/hydrated with real data when you access the items in the collection.
The use of proxy objects is the reason why you have to make all of your properties virtual in your entity types. The Proxy types are dynamically-generated subclasses of your entity type, which make the actual calls to the database when you access the properties.
Hopefully I understood your question, but the difference is that you get actual objects back, not an empty list. If you get back an empty list, it means that there were no AuditLogEntry items referencing your User in the database.