NHibernate: Persist an object with children - nhibernate

I am trying to persist an object with a collection of child objects. I can't persist the children first as there is a FK relationship. I could save the parent first and then add the children on to it, but this would introduce more work. Basically I'm just trying to save a fully populated object in one step and not break it into parts. Is there something wrong with my mapping (sorry it looks so ugly) or is it my methods?
Parent:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NetworkOrderManagement.Core.Order, NetworkOrderManagement.Core" table="NETORDMGMT.ORDERHEADER" lazy="false" >
<id name="OrderId" column="ORDERID" type="int">
<generator class="seqhilo">
<param name="sequence">ORDERID_SEQ</param>
</generator>
</id>
<property name="TransmissionDate" column="TRANSMISSIONDATE" type="DateTime"/>
<property name="StoreNumber" column="STORENUMBER" type="Int16"/>
<property name="Department" column="DEPARTMENT" type="Int16"/>
<property name="OrderType" column="ORDERTYPE" type="Int16"/>
<property name="OrderSequence" column="ORDERSEQUENCE" type="Int16"/>
<property name="ExtractTime" column="EXTRACTTIME" type="DateTime"/>
<property name="Status" column="STATUS" type="Int16"/>
<property name="ReceivedTime" column="RECEIVEDTIME" type="DateTime"/>
<bag name="OrderDetail" table="NETORDMGMT.ORDERDETAIL"
lazy="false" cascade="all" inverse="true">
<key column="ORDERID" on-delete="cascade"/>
<one-to-many class="NetworkOrderManagement.Core.OrderDetail, NetworkOrderManagement.Core" />
</bag>
</class>
</hibernate-mapping>
Child:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NetworkOrderManagement.Core.OrderDetail, NetworkOrderManagement.Core" table="NETORDMGMT.ORDERDETAIL" lazy="false">
<id name="OrderDetailId" column="ORDERDETAILID" type="int">
<generator class="seqhilo">
<param name="sequence">"ORDERDTLID_SEQ"</param>
</generator>
</id>
<many-to-one name="Order" class="NetworkOrderManagement.Core.Order, NetworkOrderManagement.Core"
column="OrderId" lazy="false" not-null="true" />
<property name="ItemNumber" column="ITEMNUMBER" type="Int32"/>
<property name="OrderQuantity" column="ORDERQUANTITY" type="Int32"/>
<property name="ErrorCode" column="ERRORCODE" type="Int32"/>
</class>
</hibernate-mapping>
Here's my exception:
Test method NetworkOrderManagement.Tests.DataAccess.QuickTests.QuickTest threw exception:
Distribution.Exceptions.DataAccessException: NHibernate Exception --->
NHibernate.PropertyValueException: not-null property references a null or transient valueNetworkOrderManagement.Core.OrderDetail.Order.
I get this when my test below tries to add an orderdetail to the order while it is still transient:
[TestMethod]
public void QuickTest()
{
myOrderRepository = NetworkOrderManagement.Data.RepositoryFactory.Instance.GetOrderRepository();
myOrderDetailRepository = NetworkOrderManagement.Data.RepositoryFactory.Instance.GetOrderDetailRepository();
myOrder = new Order { StoreNumber = RandGen.LittleRand(), Department = RandGen.LittleRand(), TransmissionDate = DateTime.MinValue, ExtractTime = DateTime.MinValue, ReceivedTime = DateTime.MinValue };
myOrder = myOrderRepository.Save(myOrder);
myOrderDetail1 = new OrderDetail {OrderId = myOrder.OrderId, ItemNumber = RandGen.BigRand(), OrderQuantity = RandGen.LittleRand() };
myOrderDetail2 = new OrderDetail {OrderId = myOrder.OrderId, ItemNumber = RandGen.BigRand(), OrderQuantity = RandGen.LittleRand() };
myOrderDetail1 = myOrderDetailRepository.Save(myOrderDetail1);
myOrderDetail2 = myOrderDetailRepository.Save(myOrderDetail2);
myOrder.OrderDetail.Add(myOrderDetail1);
myOrder.OrderDetail.Add(myOrderDetail2);
myOrderRepository.CommitChanges();
myOrderDetailRepository.Delete(myOrderDetail2);
myOrderRepository.CommitChanges();
myOrderRepository.Delete(myOrder);
myOrderRepository.CommitChanges();
}

Specify cascading on the collection, and let NHibernate figure it out for you
http://ayende.com/Blog/archive/2006/12/02/NHibernateCascadesTheDifferentBetweenAllAlldeleteorphansAndSaveupdate.aspx
http://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/example-parentchild.html
Okay, I've seen that you've done that. :)
What you haven't done, is specify the back-reference.
I mean: you add an item to your collection, but this added item has a property to its owner, which you haven't set:
Order o = new Order();
OrderDetail detail = new OrderDetail ();
detail.Order = o;
o.OrderLines.Add (detail);
What would be even better (imho) is this (simplified) :
public class Order
{
private IList<OrderDetail> _details = new List<OrderDetail>();
public ReadOnlyCollection<OrderDetail> Details
{
return new List(_details).AsReadOnly();
}
public void AddOrderLine( OrderDetail d )
{
d.Order = this;
_details.Add (d);
}
public void RemoveOrderLine( OrderDetail d )
{
_details.Remove (d);
}
}

Related

Hibernate: join with 2 NOT primary key

i want to do a left outer join from 2 table, but with 2 key that are not primary key.
This is the native sql query used:
Maybe there is a better way to do it?
SQLQuery query = session.createSQLQuery("
select {A.*},{B.*} from A_TABLE A left outer join B_TABLE B on A.QUOTE_ID = B.QUOTE_NUM ")
.addEntity("A", A_Class.class)
.addJoin("B", "A.bDocs");
for(Object result : query.list())
{
....
}
The mapping A file:
<class name="A_Class" table="A_TABLE" schema="S">
<id name="rowId" type="string">
<column name="ROW_ID" length="60" />
<generator class="assigned" />
</id>
<set name="BDocs" inverse="true" fetch="select" lazy="false">
<key>
<column name="QUOTE_NUM" length="60" not-null="true" />
</key>
<one-to-many class="B_Class" />
</set>
A_Class.java
public class A_Class implements java.io.Serializable
{
private String rowId;
private String QuoteId;
private Set BDocs= new HashSet(0);
// omitted all the set and get
}
The mapping B file:
<hibernate-mapping>
<class name="B" table="B_TABLE" schema="S">
<id name="rowId" type="string">
<column name="ROW_ID" length="60" />
<generator class="assigned" />
</id>
<many-to-one name="A" class="A_Class" fetch="select" lazy="false" outer-join="true" foreign-key="QuoteId" property-ref="QuoteId">
<column name="QUOTE_NUM" length="60" not-null="true" />
</many-to-one>
B_Class.java
public class B_Class implements java.io.Serializable
{
private String rowId;
private String quoteNum;
// omitted all the set and get
}
From this query i obtain 2 objects, one of A type and the other of B Type with a lot of correct datas but the set BDocs in the object of A type isn't filled. The goal is to get only the A Object with the variable BDocs filled with the B Objects.
I don't understand if the problem is in the query or in the mapping files. Anyone can help me?

Another NHibernate Mapping Mystery! Getting count in hbm file

I am trying to retrieve the count of items allocated to a container in my hbm file. I've done a bit of digging and managed to get my hbm code this far (below!). I want the count to be retrieved every time a container object is queried. I could use an interceptor but I assume there's a better way. Am I on the right track or should I use a different strategy to get the count loaded up?
Thanks.
P.S. We're using NH v2.2
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false" assembly="MyEntities" namespace="Entities.Containers"> <class name="Container" table="[Container]" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="Capacity" column="Capacity">
<column name="Capacity" />
</property>
<property name="Description" column="Description" length="50" type="String">
<column name="Description" />
</property>
<loader query-ref="sqCurrentContainerAllocation"/> </class>
<sql-query name="sqCurrentContainerAllocation">
<return-scalar column="AllocatedItemsCount" type="int"></return-scalar>
SELECT COUNT(*) FROM [ContainerTracking]
WHERE [ContainerId] = :Id </sql-query>
</hibernate-mapping>
If you need to get some calculated property, you can use mapping with formula.
Let's extend your C# class:
public class Container
{
... // ID, Capacity, Description
public virtual int MyCount { get; set; }
And extend your mapping
<class name="Container" table="[Container]"
...
<property name="MyCount" insert="false" update="false" >
<formula>
(
SELECT count(*)
FROM [ContainerTracking] as ct
WHERE ct.[ContainerId] = Id
)
</formula>
</property>
the Id will be replaced with somethink like '_this.Id', the name of the column Id and its alias
This will of course load the count all the time (except projections) so think twice before use it

HQL Query SELECT with NOT IN

I need a HQL Query but I just dont get it.
SELECT * FROM Poll WHERE pid NOT IN (
SELECT pid FROM votes WHERE uid = 'theuserid')
I want all List of PollĀ“s back where the uid not in the table votes exists.
Also helpfull would be the hql query where the uid in the table votes exists, but I guess this is very similar ;-)
These are the 2 classes:
public class Poll {
private int pid;
private String name;
private String description;
private Date deadline;
private Set<Team> teams = new HashSet<Team>(0);
//some getter & setter
}
public class Vote {
private int vid;
private String uid;
private int pid;
private int tid;
private int votes;
//some getter & setter
}
Can smbdy please help me. I guess it is a join with a WHERE and NOT LIKE but I just dont get it.
Merci!
This is btw the hibernate mapping:
<hibernate-mapping>
<class name="package.model.Poll" table="poll">
<id name="pid" column="pid" >
<generator class="increment"/>
</id>
<property name="name" column="name" />
<property name="description" column="description" />
<property name="deadline" type="timestamp" column="deadline" />
<set name="teams" table="pollchoice"
inverse="false" lazy="false" fetch="select" cascade="all" >
<key>
<column name="pid" not-null="true" />
</key>
<many-to-many entity-name="knt.exceedvote.model.Team">
<column name="tid" not-null="true" />
</many-to-many>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="package.model.Vote" table="votes">
<id name="vid" column="vid" >
<generator class="increment"/>
</id>
<property name="pid" column="pid" />
<property name="uid" column="uid" />
<property name="tid" column="tid" />
<property name="votes" column="votes" />
</class>
</hibernate-mapping>
Please keep in mind, Hibernate is designed using the notion of Object Graph, which is a name given to the relational objects.
That core concept is missing in your mapping (Poll and Vote seem to be isolated) hence I doubt you can use HQL in its current state.
In my opinion, you have two options:
Define the relationship between Poll, pid and Vote, uid. Then you should be able to write simple HQL.
Use native SQL through Hibernate session itself.

NHibernate.PropertyValueException : not-null property references a null or transient

I am getting the following exception.
NHibernate.PropertyValueException : not-null property references a null or transient
Here are my mapping files.
Product
<class name="Product" table="Products">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<set name="PriceBreaks" table="PriceBreaks" generic="true" cascade="all" inverse="true" >
<key column="ProductId" />
<one-to-many class="EStore.Domain.Model.PriceBreak, EStore.Domain" />
</set>
</class>
Price Breaks
<class name="PriceBreak" table="PriceBreaks">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="ProductId" column="ProductId" type="Int32" not-null="true" />
<many-to-one name="Product" column="ProductId" not-null="true" cascade="all" class="EStore.Domain.Model.Product, EStore.Domain" />
</class>
I get the exception on the following method
[Test]
public void Can_Add_Price_Break()
{
IPriceBreakRepository repo = new PriceBreakRepository();
var priceBreak = new PriceBreak();
priceBreak.ProductId = 19;
repo.Add(priceBreak);
Assert.Greater(priceBreak.Id, 0);
}
Following up on Jan reply. I've removed the ProductId from priceBreak map. This works!!
public int AddPriceBreak(Product product, PriceBreak priceBreak)
{
using (ISession session = EStore.Domain.Helpers.NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
product.AddPriceBreak(priceBreak);
session.SaveOrUpdate(product);
transaction.Commit();
}
return priceBreak.Id;
}
Remove the ProductId property from the mapping and from the PriceBreak class. And use the PriceBreaks collection to add PriceBreaks, you don't need the PriceBreakRepository, but only a ProductRepository.
Example:
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var product = session.Get<Product>(19);
product.AddPriceBreak(new PriceBreak());
tx.Commit();
}
}
And in the Product:
class Product
{
// ...
public void AddPriceBreak(PriceBreak pb)
{
pb.Product = this;
PriceBreaks.Add(pb);
}
}
Your usage of Id properties along with the actual references is incorrect.
First, remove this line:
<property name="ProductId" column="ProductId" type="Int32" not-null="true" />
Then, instead of assigning ProductId (you should remove that property completely), use:
priceBreak.Product = session.Load<Product>(19);
(You might need to add the Load method to your repository).

Nhibernate query issue

Considering the following mapping file where TemporaryAccessOpenCommand and TemporaryAccessCloseCommand both inherit from base class Command
<class name="Command" table="xxx_Commands" lazy="false">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="BeginDate" />
<property name="EndDate" />
<component name="Result" class="CommandResult">
<property name="Status" column="ResultStatus"/>
<property name="Details" column="ResultDetails" />
</component>
<many-to-one name="Requestor" class="xxx.Domain.SessionInfo, xxx.Domain" column="SessionInfoId" lazy="false" />
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Close">
<key column="CommandId"/>
</joined-subclass>
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessOpenCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Open">
<key column="CommandId"/>
<many-to-one name="EndUser" ...... />
<property name="Reason"/>
<property name="AccessRight"/>
<property name="AccessType"/>
<bag name="CloseAccessCommands" cascade="all" lazy="false">
<key column="OpenCommandId"/>
<one-to-many class="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess"/>
</bag>
</joined-subclass>
What would be the nhibernate query to retrieve every OpenAccessCommand having no succeeded CloseAccessCommand ?
I tried this :
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.CreateCriteria("CloseAccessCommands")
.Add(Expression.Not(Expression.Eq("Result.Status", CommandStatus.Succeeded)))
.List<TemporaryAccessOpenCommand>();
});
}
But it will return an OpenAccessCommand that has tow CloseCommand (one failed and one succeeded) when it should return an empty list.
Thanks for your help (and excuse my poor english)
I managed to get something working this way (I'm open to suggestions if it's not the best way)
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
string sql = string.Format(#"
(
SELECT temp_c.OpenCommandId
FROM VSA2_Commands_TemporaryAccess_Close temp_c
INNER JOIN VSA2_Commands c
ON temp_c.CommandId = c.Id
WHERE c.ResultStatus = {0}
) AS OpenCommandId"
, (int)CommandStatus.Succeeded);
var subCriteria = DetachedCriteria.For<TemporaryAccessCloseCommand>();
subCriteria = subCriteria.SetProjection(Projections.SqlProjection(sql, new string[] { "OpenCommandID" }, new IType[] { NHibernateUtil.Int32 }));
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.Add(Expression.Eq("Result.Status", CommandStatus.Succeeded))
.Add(Subqueries.PropertyNotIn("Id", subCriteria))
.List<TemporaryAccessOpenCommand>();
});
}