views:

136

answers:

1

Hi,

I am in the process or learning NHibernate so bare 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

A: 

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.

Jamie Ide
@Jamie - this certainly works although it still looks like an update query is executed for each transaction belonging to an order. I guess I thought NH would figure out what had changed and only issue an insert for any new transactions and an update for any transactions that have changed. Maybe I'm asking too much? :)
Ben
No, that's the way it should work. You probably have a "ghost" problem, i.e. a value is changed unexpectedly. Set `dynamic-update="true"` in the Transaction mapping and look at the SQL that is being issued (through a profiler or logging). You will probably find that your object is inadvertently altering a value.
Jamie Ide
@Jamie I have reviewed the SQL that is generated and I am not changing any values on the existing transactions. My sample code retrieves an existing order, adds a new transaction to the order, then saves the order. If I four existing transactions for that order the total SQL executed is as follows1. select * from order where id = blah2. select * from transactions where orderId = blah3. Insert into transactions (new transaction)4. Update order (sets values to same as original)5. Update transaction 1,2,3,4 (sets value to same as before)
Ben
That's odd. Does it update all columns even if you include `dynamic-update="true"`? I almost always use bag/IList initialized as List implementations so it may be related to use of set/ICollection initialized as HashSet.
Jamie Ide
@Jamie - yes I have dynamic-update="true" set and it still issues an update for each transaction. I swapped out the set/hashset for a bag/ICollection and experienced the same problem. Have you got a basic example (or a link to one) of how this should work and perhaps I can review my own code. Have only been using NH for 2 days now so difficult to tell what is a bug what is by design :(
Ben
Does the update include every column? Dynamic-update="true" should cause it to only include properties that have changed. Hopefully that will point you to the root cause.
Jamie Ide
Hi Jamie, yes every column is updated. After reading up on the NH wiki I thought it was perhaps because I had not told NH how to compare my objects, so I overrided Equals on both Order and Transaction objects but this made no difference, I still get an update command generated for every transaction belonging to that object and every field is updated.
Ben