Data is not inserted in Many to Many mapping? - hibernate-mapping

i have three DB tables
route(route_id)
stop(stop_id)
route_stop(route_id,stop_id)---mapping table
Route.hbm.xml
<set name="stops" table="route_stop" cascade="all"><key column="route_id" /><many-to-many column="stop_id" class="com.trackingsystem.model.Stop" /></set>
Stop.hbm.xml
<set name="routes" table="route_stop" cascade="all"><key column="stop_id" /><many-to-many column="route_id" class="com.trackingsystem.model.Route" />
but data is not inserted in the DB,
it is showing these select queries
Hibernate:
/* get current state com.trackingsystem.model.Stop */ select
stop_.stop_id,
stop_.stop_name as stop2_106_,
stop_.lattitude as lattitude106_,
stop_.langitude as langitude106_
from
trackingsystem.stop stop_
where
stop_.stop_id=?
Hibernate:
/* get current state com.trackingsystem.model.Stop */ select
stop_.stop_id,
stop_.stop_name as stop2_106_,
stop_.lattitude as lattitude106_,
stop_.langitude as langitude106_
from
trackingsystem.stop stop_
where
stop_.stop_id=?
where is the problem?

The problem is i didn't flush the session. After that every thing is ok.
thanks.

Related

sql alias to dictionary returns duplicate results

I have following class
<class name="Product" table="Product">
<id name="ID" />
...
<map name="CustomFields" table="CustomFieldView">
<key column="RECORDID" />
<map-key column="CFName" type="String" />
<element column="CFValue" type="String" />
</map>
</class>
and SP to select product with CustomFields dictionary
<sql-query name="GetProducts">
<return alias="p" class="Product" />
<return-join alias="cf" property="p.CustomFields" />
SELECT {p.*}, {cf.*}
FROM Product p
INNER JOIN CustomFieldView cf ON p.ID = cf.RECORDID
// WHERE
</sql-query>
when I select single product like WHERE ID = 1234, then it works as expected - returns one Product with populated CustomFields Dictionary property.
But when I select not single Product like WHERE ID IN (18780, 21642) or other criterias then I get Products duplicated 'CustomFields.Count' times, e.g. 2 Products and each has 20 Custom Fields, then 40 Products and each has 20 valid custom fields.
Do I missed something in mapping ?
You are returning a Cartesian product and therefore your product is being returned x times for every custom field.
To get around this problem you will need to use something like:-
var query = Session
.GetNamedQuery("GetProducts")
.SetResultTransformer(new DistinctRootEntityResultTransformer());
return query.List<Product>();
Please note that you will send all the data down the wire and NHibernate will perform the distinct transformer client (meaning web server or desktop app) side.
I am not 100% sure if the return join will be populated as I have never done things this way, you will need to test this.
edit
I think you fetching strategy is not quite right. Do you really need a <sql-query...> Could you use another strategy e.g. HQL?

How do I use NHibernate criteria queries to load associations based on additional conditions

Let's say I have a Cat that has two properties:
FavoriteKitten
SecondFavoriteKitten
These kittens are discriminated by their Rank.
When loading a Cat, I want the kitten with the rank of "1" to be
FavoriteKitten, and the kitten with the rank of "2" to be
SecondFavoriteKitten.
The underlying database looks like:
table Cat
----------------
CatId
table Kitten
-----------------
KittenId
CatId
Rank
My mapping looks like:
<class name="Cat">
... other stuff
<one-to-one name="FavoriteKitten" class="Kitten" property-ref="Cat" cascade="all-delete-orphan" />
<one-to-one name="SecondFavoriteKitten" class="Kitten" property-ref="Cat" cascade="all-delete-orphan" />
</class>
My criteria query looks like
Cat cat = sess.CreateCriteria(typeof(Cat))
.CreateAlias("FavoriteKitten", "kt1")
.Add(Expression.Eq("kt1.Rank", "1"))
.CreateAlias("SecondFavoriteKitten", "kt2")
.Add(Expression.Eq("kt2.Rank", "2"))
.UniqueResult();
The trouble is that once loaded, both FavoriteKitten and
SecondFavoriteKitten are the same kitten: the one with a Rank of "2".
Have I left something out of the criteria? Or am I going about this
the wrong way?
Diego, over in the nhibernate mailing list, helped me see the error of my ways. I had everything structured wrong.
Per his suggestion, I decided to map Kittens as it is in the database; as lists.

NHibernate - create criteria based on a property that is a list

I have a class called LoanApplication, and it has a collection property set up called Workflow. In the mapping file, I am setting the order of retrieval of the Workflow records to sort by Date, so the current workflow is always the first item in the list.
Now I want to query by the current workflow to get LoanApplications that are in a specific workflow step using the Criteria API. I'm not really sure how to do this. Here is how I am mapping the Workflow collection:
<bag name="ApplicationWorkflow" table="PreApplication.ApplicationWorkflow" generic="true" inverse="true" order-by="StartDate DESC"
cascade="all" lazy="true">
<key column="ApplicationID" />
<one-to-many class="ApplicationWorkflow" />
</bag>
Here is how I am retrieving applications (this is where I need to add the filter by Current Workflow functionality):
public IList<Model.PreApplication.Application> GetCompletedApplications()
{
IList<Model.PreApplication.Application> result = null;
using (ITransaction transaction = this.Session.BeginTransaction())
{
result = this.Session.CreateCriteria<Model.PreApplication.Application>()
.AddOrder(new Order("EnteredDate", false))
.List<Model.PreApplication.Application>();
transaction.Commit();
}
return result;
}
Thanks for any help!
So you need to list Applications whose current Workflow is at a specific step? You can use a sub-query to join only the current workflow and then restrict to a specific step.
the desired SQL...
select
...
from
Application app
inner join ApplicationWorkFlow currWorkFlow on app.id = currWorkFlow.application_id
where
currWorkFlow.id = (
select top 1 topFlow.id
from ApplicationWorkFlow topFlow
where topFlow.application_id = app.id
order by topFlow.StartedDate desc
)
and currWorkFlow.step = #step
the Criteria to get you there...
session.CreateCriteria<Application>("app")
.CreateAlias("ApplicationWorkFlow", "currWorkFlow", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(Subqueries.PropertyEq("currWorkFlow.id",
DetachedCriteria.For<ApplicationWorkFlow>("topFlow")
.SetMaxResults(1)
.SetProjection(Projections.Property("topFlow.id"))
.AddOrder(Order.Desc("topFlow.StartDate"))
.Add(Restrictions.EqProperty("app.id", "topFlow.Application.id"))))
.Add(Restrictions.Eq("currWorkFlow.step", step))
.List<Application>();
How about this sql query?
SELECT * FROM Application app
JOIN
(SELECT * FROM ApplicationWorkFlow aflo WHERE aflo.step = #step) AS aflo
WHERE aflo.application_id = app.id
Simplified query using criteria
var applications =
Session.CreateCriteria<Application>()
.CreateAlias("ApplicationWorkFlow", "appflo", JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("appflo.Step", step))
.List<Application>();
Corresponding Criteria Query using detached criteria
var detached = DetachedCriteria.For<ApplicationWorkFlow>()
.SetProjection(Projections.Id())
.Add(Restrictions.Eq("Step", step));
var applications =
Session.CreateCriteria<Application>()
.CreateAlias("ApplicationWorkFlow", "appflo", JoinType.LeftOuterJoin)
.Add(Subqueries.PropertyIn("appflo.Id", detachedCriteria))
.List<Application>();
Could someone please tell me if the above two queries are the same? They are generating the same sql in my case. If they are the same, why should be use the DetachedCriteria?
I suggest to add simply reference to last/active Workflow. Criteria will be much more simpler :)
public void AddLatestWorkflow(Workflow wf)
{
this.Workflows.Add(wf);
this.LatestWorkflow = wf;
}

Hibernate inner join mapping - string where id

I'd like to map the following sql in NHibernate.
Will I need to make a separate entity object i.e RoomTypeVO mapped to tb_tags to do this?
Any help much appreciated.
SELECT
dbo.tb_rooms.id,
dbo.tb_rooms.name,
dbo.tb_tags.name AS 'roomType'
FROM
dbo.tb_rooms
INNER JOIN dbo.tb_tags ON (dbo.tb_rooms.typeID = dbo.tb_tags.id)
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" />
If you to a straight sql query you do not have to. If you want to use HQL you will have to work with an entity.
But, you can always do sql queries directly.
If you have a mapped entity then you could probably just do something like this:
FROM RoomType
When you refer to 'FROM', are you thinking of something like this?
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>

NHibernate Eager Fetching Over Multiple Levels

I have a 3-leveled hierarchy of entities: Customer-Order-Line, which I would like to retrieve in entirety for a given customer, using ISession.Get(id). I have the following XML fragments:
customer.hbm.xml:
<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join">
<key column="CustomerID" />
<one-to-many class="Order" />
</bag>
order.hbm.xml:
<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join">
<key column="OrderID" />
<one-to-many class="Line" />
</bag>
I have used the fetch="join" attribute to indicate that I want to fetch the child entities for each parent, and this has constructed the correct SQL:
SELECT
customer0_.ID AS ID8_2_,
customer0_.Name AS Name8_2_,
orders1_.CustomerID AS CustomerID__4_,
orders1_.ID AS ID4_,
orders1_.ID AS ID9_0_,
orders1_.PostalAddress AS PostalAd2_9_0_,
orders1_.OrderDate AS OrderDate9_0_,
lines2_.OrderID AS OrderID__5_,
lines2_.ID AS ID5_,
lines2_.ID AS ID10_1_,
lines2_.[LineNo] AS column2_10_1_,
lines2_.Quantity AS Quantity10_1_,
lines2_.ProductID AS ProductID10_1_
FROM Customer customer0_
LEFT JOIN [Order] orders1_
ON customer0_.ID=orders1_.CustomerID
LEFT JOIN Line lines2_
ON orders1_.ID=lines2_.OrderID
WHERE customer0_.ID=1
So far, this looks good - SQL returns the correct set of records (with only one distinct orderid), but when I run a test to confirm the correct number of entities (from NH) for Orders and Lines, I get the wrong results
I should be getting (from my test data), 1xOrder and 4xLine, however, I am getting 4xOrder and 4xLine. It appears that NH is not recognising the 'repeating' group of Order information in the result set, nor correctly 'reusing' the Order entity.
I am using all integer IDs (PKs), and I've tried implementing IComparable of T and IEquatable of T using this ID, in the hope that NH will see the equality of these entities. I've also tried overridding Equals and GetHashCode to use the ID. Neither of these 'attempts' have succeeded.
Is "multiple leveled fetch" a supported operation for NH, and if so, is there an XML setting required (or some other mechanism) to support it?
NB: I used sirocco's solution with a few changes to my own code to finally solve this one. the xml needs to be changed from bag to set, for all collections, and the entitities themselves were changed to implement IComparable<>, which is a requirement of a set for uniqueness to be established.
public class BaseEntity : IComparable<BaseEntity>
{
...
private Guid _internalID { get; set; }
public virtual Guid ID { get; set; }
public BaseEntity()
{
_internalID = Guid.NewGuid();
}
#region IComparable<BaseEntity> Members
public int CompareTo( BaseEntity other )
{
if ( ID == Guid.Empty || other.ID == Guid.Empty )
return _internalID.CompareTo( other._internalID );
return ID.CompareTo( other.ID );
}
#endregion
...
}
Note the use of an InternalID field. This is required for new (transient) entities, other wise they won't have an ID initially (my model has them supplied when saved).
You're getting 4XOrder and 4XLines because the join with lines doubles the results . You can set a Transformer on the ICriteria like :
.SetResultTransformer(new DistinctRootEntityResultTransformer())
I just read Ayende's Blogpost where he used the following Example:
session.CreateCriteria(typeof(Post))
.SetFetchMode("Comments", FetchMode.Eager)
.List();
In a Criteria Query to avoid Lazy Loading on one particular Query
Maybe that can help you.
If you need to keep your one-to-manys as bags, then you can issue 2 queries, each with only 1 level of hierarchy. eg something like this:
var temp = session.CreateCriteria( typeof( Order ) )
.SetFetchMode( "Lines", NHibernate.FetchMode.Eager )
.Add( Expression.Eq( "Customer.ID", id ) )
.List();
var customer = session.CreateCriteria( typeof( Customer ) )
.SetFetchMode( "Orders", NHibernate.FetchMode.Eager )
.Add( Expression.Eq( "ID", id ) )
.UniqueResult();
Lines get loaded into the NH cache in the first query, so they won't need lazy loading when later accessing eg customer.Orders[0].Lines[0].
#Tigraine: your query only returns Post with Comments. This brings All posts with all Comments (2 levels). What Ben asking is Customer to Order To LineItem (3 level).
#Ben: to my knowledge nHibernate doesn't support eager loading upto 3 level yet. Hibernate does support it thou.
I was having the same problem. See this thread. I didn't get a solution but a hint from Fabio. Use Set instead of bag. And it worked.
So my suggestion is try to use set. You don't have to use Iesi collection use IDictonary and NH is happy
public override IEnumerable<Baseline> GetAll()
{
var baselines = Session.CreateQuery(#" from Baseline b
left join fetch b.BaselineMilestones bm
left join fetch bm.BaselineMilestonePrevious ")
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Baseline>();
return baselines;
}