Saving child collections with NHibernate - 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.

Related

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

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!

Loading a collection of union-subclass entities polymorphically - column specified multiple times

Take the following entities:
public class Company : Entity<Guid>
{
public virtual string Name { get; set; }
public virtual IList<IEmployee> Employees { get; set; }
public Company()
{
Id = Guid.NewGuid();
Employees = new List<IEmployee>();
}
}
public interface IEmployee
{
Guid? Id { get; set; }
string Name { get; set; }
void Work();
Company Company { get; set; }
}
public class ProductionEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public ProductionEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm making the stuff.");
}
}
public class SalesEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public SalesEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm selling the stuff.");
}
}
Mapped in the following way in NHibernate:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="Company" table="`Company`">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<property name="Name" column="`Name`"/>
<bag name="Employees" inverse="true" cascade="save-update">
<key column="CompanyId"></key>
<one-to-many class="IEmployee" />
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="IEmployee" abstract="true">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<many-to-one name="Company" column="`CompanyId`" cascade="save-update"/>
<union-subclass name="ProductionEmployee" table ="`ProductionEmployee`" >
</union-subclass>
<union-subclass name="SalesEmployee" table ="`SalesEmployee`">
</union-subclass>
</class>
</hibernate-mapping>
If I create a Company entity and add IEmployee entities to its collection (also setting the Company property of the IEmployee entity to create the bi-directional relationship), Then when I save the company, everything goes into the database as expected. The companyId is set correctly on the PoductionEmployee and SalesEmlpoyee records.
But when I come to load it, I get the following error:
The column 'CompanyId' was specified multiple times for 'employees0_'
The generated SQL looks like this:
SELECT employees0_.CompanyId as CompanyId1_, employees0_.Id as Id1_, employees0_.Id as Id9_0_, employees0_.[CompanyId] as CompanyId2_9_0_, employees0_.clazz_ as clazz_0_
FROM ( select Id, CompanyId, CompanyId, 1 as clazz_ from [ProductionEmployee] union all select Id, CompanyId, CompanyId, 2 as clazz_ from [SalesEmployee] ) employees0_
WHERE employees0_.CompanyId=?
Why is it generating the CopmanyId column twice and how do I prevent this?
Nothing to do with union-subclass in the end. The problem was in the one-to-many collection I had specified column="CompanyId", and in the many-to-one I had specified column="`CompanyId`". Including the backticks in one and not the other had caused NHibernate to think they were different column. Never come across this in all my time using NHibernate.

NHibernate Stale State Issue

I'm curious if anyone could help me resolve an issue of stale state in nHibernate.
First, the .Net class code:
public class Test
{
public static Test Get(int testId) { return Factory.GetTest(testId); }
public Test() { Related = new List<TestRelate>(); }
public virtual int ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<TestRelate> Related { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
public class TestRelate
{
protected TestRelate() { }
public TestRelate(Test test) { TestID = test.ID; }
public virtual int ID { get; protected set; }
public virtual int TestID { get; set; }
public virtual string Data { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
class Factory
{
public static Test GetTest(int testId)
{
ISession session = Session.HybridSessionBuilder.Instance;
IList<Test> ret = null;
ITransaction tx = null;
tx = session.BeginTransaction();
ret = session.CreateCriteria(typeof(Test))
.Add(Expression.Eq("ID", testId))
.List<Test>();
tx.Commit();
return ret.Count == 0 ? null : ret[0];
}
public static void Save<T>(T element)
{
ISession session = Session.HybridSessionBuilder.Instance;
ITransaction tx = null;
tx = session.BeginTransaction();
session.Save(element);
tx.Commit();
}
public static void Delete<T>(T element)
{
ISession session = Session.HybridSessionBuilder.Instance;
ITransaction tx = null;
tx = session.BeginTransaction();
session.Delete(element);
tx.Commit();
}
}
Then the nHibernate mapping XML:
<class name="Data.Test.Test, Data" table="test_info">
<id name="ID" column="testid">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Related" table="test_relate" lazy="false" cascade="none">
<key column="testid"></key>
<one-to-many class="Data.Test.TestRelate, Data"></one-to-many>
</bag>
</class>
<class name="Data.Test.TestRelate, Data" table="test_relate">
<id name="ID" column="relateid">
<generator class="native" />
</id>
<property name="TestID" />
<property name="Data" />
</class>
And finally the code I'm having trouble with:
Data.Test.Test Test = new Data.Test.Test();
Test.Name = "Hello World";
Test.Save();
Data.Test.TestRelate Relate = new Data.Test.TestRelate(Test);
Relate.Data = "How are you?";
Relate.Save();
Test = Data.Test.Test.Get(Test.ID);
int Count = Test.Related.Count;
The problem is that the Test.Related list is always empty when I run this code. However if I destroy the NHibernate session and load up the Test again it populates the list as expected. I realize I could probably flush all caching data but it seems like there should be a cleaner solution to this issue. Any suggestions?
When you do new Data.Test.TestRelate(Test) there is nothing that adds the new TestRelate instance to the collection in the owner Test. (Unless you do that in the constructor, but I assume you only set TestId there).
You should Add() the new TestRelate instance to Test.Related. Nhibernate will notice the change in the collection and save the new item when the session is flushed.
NHibernate doesn't populate one-to-many collections automatically on commit. You should simply add TestRelate instances to the Related list, as you would do without NHibernate, and then (if you set a "cascade save" mapping) even commit the Test instance only.
There is no need to use the TestID property inside the program at all, as this property is actually only a byproduct of relational DB mapping.
Alright so I realized that my approach was due to some past failed attempts at utilizing NHibernate's cascading. I'll go over each one of the issues and what I did to resolve it.
If I set up cascading saves NHibernate would fail when I would try to add Related elements to a new Test element because the TestID value is not allowed to be null in the database. Altering the property from an integer type to the Test type itself remedied this situation as NHibernate was able to populate the field value after saving the new Test element.
Attempting to delete a Related record by removing it from the list would result in an error due to NHibernate attempting to Update the TestID field to null prior to a delete. Adding the inverse="true" attribute to the Bag mapping element resolved this issue.
Deleting the Test object would not delete the orphaned Related records. Setting the cascade attribute to all-orphan-delete remedied this.
Here's all the new code (there were no changes to the Factory class):
public class Test
{
public static Test Get(int testId) { return Factory.GetTest(testId); }
public Test() { Related = new List<TestRelate>(); }
public virtual int ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<TestRelate> Related { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
public class TestRelate
{
protected TestRelate() { }
public TestRelate(Test test) { Test = test; }
public virtual int ID { get; protected set; }
public virtual Test Test { get; set; }
public virtual string Data { get; set; }
public virtual void Delete() { Factory.Delete(this); }
public virtual void Save() { Factory.Save(this); }
}
Mapping changes:
<class name="Data.Test.Test, Data" table="test_info">
<id name="ID" column="testid">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Related" table="test_relate" lazy="false" cascade="all-delete-orphan" inverse="true">
<key column="testid"></key>
<one-to-many class="Data.Test.TestRelate, Data"></one-to-many>
</bag>
</class>
<class name="Data.Test.TestRelate, Data" table="test_relate">
<id name="ID" column="relateid">
<generator class="native" />
</id>
<many-to-one name="Test" column="testid" />
<property name="Data" />
</class>
The following code now behaves as expected:
Data.Test.Test Test;
Data.Test.TestRelate Relate;
Test = new Data.Test.Test();
Test.Name = "Hello World";
Relate = new Data.Test.TestRelate(Test);
Relate.Data = "How are you?";
Test.Related.Add(Relate);
Test.Save();
Relate = new Data.Test.TestRelate(Test);
Relate.Data = "Relate #2";
Test.Related.Add(Relate);
Test.Save();
Test.Related.RemoveAt(0);
Test.Save();
Test = Data.Test.Test.Get(Test.ID);
int Count = Test.Related.Count;
Test.Delete();
I was able to glean most of these answers from http://ayende.com . I highly recommend this site as a resource for nHibernate questions.

NHibernate mapping

I use HBM mapping.
I have tables :
I) person with columns :
1. ID
2. TYPE
3.CREATE_DATE
4.UPDATE_DATE
II) Attribute with columns:
1.ID
2.TYPE(in this example person may be all type)
3.NAME
4.CREATE_DATE
5.UPDATE_DATE
III) Attribute_VALUE with columns:
1.ID
2.VALUE
4.OBJECT_ID
5.ATTRIBUTE_ID
6.CREATE_DATE
7.UPDATE_DATE
There is relationship one-to-many between person(ID) and Attribute_VALUE(OBJECT_ID).
There is relationship one-to-many between Attribute(ID) and Attribute_VALUE(ATTRIBUTE_ID)
I need build object PERSON that contain all columns of person and dictionary with name attribute.
The dictionary contain key - name of attribute value- collection of values .
Can I build appropriate HBM ??
the short answer no.
the long answer:
consider how should nhibernate match attributes when you Attributes.Add("foo", "value")? it has to search the db for an attribute foo (which is not a simple mapping, its logic) or it would create a new Attribute, everytime you add one.
So given the above schema you either a) have some kind of custom onsave code (which i think is a lot of effort) or b) you change the Person to
class Person
{
public virtual int Id { get; set; }
public virtual ICollection<AttributeValue> Attributes { get; set; }
public virtual IEnumerable<string> GetValues(string attributeName)
{
return Attributes
.Where(attr => attr.Attribute.Name == attributeName)
.Select(attr => attr.Value);
}
public virtual void AddValue(Attribute attribute, string value)
{
Attributes.Add(new AttributeValue
{
Attribute = attribute,
Value = value
});
}
public virtual IEnumerable<string> GetAttributeNames()
{
return Attributes
.Select(attr => attr.Attribute.Name);
}
}
class Attribute
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
// and more Properties like created and updated
}
class AttributeValue
{
public virtual int Id { get; set; }
public virtual Attribute Attribute { get; set; }
public virtual string Value { get; set; }
// and more Properties like created and updated
}
and then use
<class name="Person" table="Persons" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<bag name="Attributes">
<key column="OBJECT_ID"/>
<one-to-many class="AttributeValue"/>
</bag>
</class>
<class name="Attribute" table="Attributes" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<property name="Name" column="Name"/>
<!--additional properties-->
</class>
<class name="AttributeValue" table="AttributeValues" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<many-to-one class="Attribute" column="ATTRIBUTE_ID"/>
<property name="Value" column="Value"/>
<!--additional properties-->
</class>

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.