NHibernate Eager Fetching Over Multiple Levels - nhibernate

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;
}

Related

NHibernate: Why does Linq First() force only one item in all child and grandchild collections with FetchMany()

Domain Model
I've got a canonical Domain of a Customer with many Orders, with each Order having many OrderItems:
Customer
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual int Id {get;set;}
public virtual ICollection<Order> Orders {get;set;}
}
Order
public class Order
{
public Order()
{
Items = new HashSet<OrderItem>();
}
public virtual int Id {get;set;}
public virtual Customer Customer {get;set;}
}
OrderItems
public class OrderItem
{
public virtual int Id {get;set;}
public virtual Order Order {get;set;}
}
Problem
Whether mapped with FluentNHibernate or hbm files, I run two separate queries, that are identical in their Fetch() syntax, with the exception of one including the .First() extension method.
Returns expected results:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).ToList()[0];
Returns only a single item in each collection:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).First();
I think I understand what's going on here, which is that the .First() method is being applied to each of the preceding statements, rather than just to the initial .Where() clause. This seems incorrect behavior to me, given the fact that First() is returning a Customer.
Edit 2011-06-17
After further research and thinking, I believe that depending on my mapping, there are two outcomes to this Method Chain:
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items);
NOTE: I don't think I can get subselect behavior, since I'm not using HQL.
When the mapping is fetch="join" I should get a cartesian product between the Customer, Order and OrderItem tables.
When the mapping is fetch="select" I should get a query for Customer, and then multiple queries each for Orders and OrderItems.
How this plays out with adding the First() method to the chain is where I lose track of what should be happening.
The SQL Query that get's issued is the traditional left-outer-join query, with select top (#p0) in front.
The First() method is translated into SQL (T-SQL at least) as SELECT TOP 1 .... Combined with your join fetching, this will return a single row, containing one customer, one order for that customer and one item for the order. You might consider this a bug in Linq2NHibernate, but as join fetching is rare (and I think you're actually hurting your performance pulling the same Customer and Order field values across the network as part of the row for each Item) I doubt the team will fix it.
What you want is a single Customer, then all Orders for that customer and all Items for all those Orders. That happens by letting NHibernate run SQL that will pull one full Customer record (which will be a row for each Order Line) and construct the Customer object graph. Turning the Enumerable into a List and then getting the first element works, but the following will be slightly faster:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();
the AsEnumerable() function forces evaluation of the IQueryable created by Query and modified with the other methods, spitting out an in-memory Enumerable, without slurping it into a concrete List (NHibernate can, if it wishes, simply pull enough info out of the DataReader to create one full top-level instance). Now, the First() method is no longer applied to the IQueryable to be translated to SQL, but it is instead applied to an in-memory Enumerable of the object graphs, which after NHibernate has done its thing, and given your Where clause, should be zero or one Customer record with a hydrated Orders collection.
Like I said, I think you're hurting yourself using join fetching. Each row contains the data for the Customer and the data for the Order, joined to each distinct Line. That is a LOT of redundant data, which I think will cost you more than even an N+1 query strategy.
The best way I can think of to handle this is one query per object to retrieve that object's children. It would look like this:
var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();
customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();
foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();
This requires a query for each Order, plus two at the Customer level, and will return no duplicate data. This will perform far better than a single query returning a row containing every field of the Customer and Order along with each Item, and also better than sending a query per Item plus a query per Order plus a query for the Customer.
I'd like to update the answer with my found so that could help anybody else with the same problem.
Since you are querying the entity base on their ID, you can use .Single instead of .First or .AsEnumerable().First():
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).Single();
This will generate a normal SQL query with where clause and without the TOP 1.
In other situation, if the result has more than one Customer, exception will be thrown so it won't help if you really need the first item of a series based on condition. You have to use 2 queries, one for the first Customer and let the lazy load do the second one.

Order By Aggregate Subquery with NHibernate ICriteria or QueryOver

Is there a way to achieve SQL like this with NHibernate ICriteria or QueryOver?
select *
from [BlogPost] b
inner join (select blogpost_id, count(*) matchCount
from [Tag]
where name in ('tag X', 'tag Y')
group by blogpost_id
) tagmatch
on tagmatch.blogpost_id = b.Id
order by tagmatch.matchCount desc
The aim is to rank blog posts by the number of matching tags so that a post with both tag X and tag Y comes above posts with just tag X.
I've got this so far:
DetachedCriteria
.For<Tag>("tag")
.Add(Restrictions.In(Projections.Property<Tag>(x => x.Name), tags.ToArray()))
.SetProjection(Projections.Group<Tag>(t => t.BlogPost))
.CreateCriteria("BlogPost")
.SetFetchMode("BlogPost", FetchMode.Eager)
.AddOrder(Order.Desc(Projections.RowCount()));
However, the resulting query doesn't join fetch BlogPost. Instead it returns just the ids, which leads to select n+1 when the BlogPosts are iterated.
public class BlogPost
{
...
ISet<Tag> Tags {get; set;}
}
public class Tag
{
BlogPost BlogPost { get; set; }
string Name { get; set; }
}
This looks like a similar issue.
Is this now possible with NHibernate 3?
If not, is there an alternative solution?
I can change schema & domain model if necessary. I don't want to use SQL or HQL if possible.
I know this question was put some time ago, but I want to do about the same thing, please take a look to my question here, and this guy here, maybe you can use the idea.

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;
}

How to query a subproperty with NHibernate’s criteria api and the entity to load only the subproperties matching a predicate condition

Assuming the following:
public class Order
{
public virtual int OrderId {get;set}
public virtual ISet<Product> Products {get;set}
}
public class Product
{
public virtual int ProductId {get;set}
public virtual string ProductName {get;set}
}
How would you query using the criteria api so that only an order with a specific orderid is returned and its Product collection should also be filtered down to Products whose Name start with the lettter P?
Simplest approach is to use an alias:
var productIdToSelect = 9;
var crit = Session.CreateCriteria(typeof(Order));
crit.CreateAlias("Product", "prod");
crit.Add(Expression.Eq("prod.Id", productIdToSelect));
var result = crit.List<Order>();
I would go about this with a DetachedCriteria:
DetachedCriteria crit = DetachedCriteria.For<Order>();
crit.Add(Restrictions.Eq("OrderId",orderID);
crit.CreateCriteria("Products","products");
crit.Add(Restrictions.Like("products.ProductName","P%");
crit.List();
and then executing the criteria and getting the results.
I don't know the code you would have to write, but a point in the right direction:
http://www.nhforge.org/doc/nh/en/index.html#querycriteria-associations (14.4)
The key seems to be:
.SetResultTransformer(CriteriaUtil.AliasToEntityMap)
The documentation shows an example with cats and kittens.
Note that the kittens collections held by the Cat instances returned by the previous two queries are not pre-filtered by the criteria! If you wish to retrieve just the kittens that match the criteria, you must use SetResultTransformer(CriteriaUtil.AliasToEntityMap).
Set up a filter on the mapping of the collection.
<filter name="letterFilter" condition="ProductName like ':letterSupplied'"/>
Then before running the Order query enable the filter
session.EnableFilter("letterFilter").SetParameter("letterSupplied", "P%");
then run the query
Order ord = session.CreateCriteria<Order>().Add(Restrictions.IdEq(suppliedId)).UniqueResult<Order>();
Note that the single quotes in the filter definition may not be required and also i place the % symbol with the supplied parameter as i don't know how NH would react a filter like
<filter name="letterFilter" condition="ProductName like ':letterSupplied%'"/>
or
<filter name="letterFilter" condition="ProductName like :letterSupplied%"/>

Only get latest results using nHibernate

I have a nHibernate query like this
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
Problem is that users add results all the time and I want to show a table of all the latest results for all the diferent ResultTypes I have.
The MyResult class has a property ResultDate. My question is, what do I add to the query to get it to only return the latest result for the given result type. There is nothing to say that the results will be in date order in the database.
Thanks,
Mark
You can order the result by ResultDate using the AddOrder method, as below:
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
.AddOrder(Order.Desc("ResultDate"))
.List<MyResult>();
If you want to limit the number of MyResult instances you get back, you can use the SetMaxResults method, like so:
ICriteria query = session.CreateCriteria(typeof(MyResult))
.Add(Expression.Eq("ResultTypeId", myResult.ResultTypeId))
.AddOrder(Order.Desc("ResultDate"))
.SetMaxResults(20)
.List<MyResult>();
If I understand the question well, Mark wants to see an overview of all the last results for each type.
Which means that, for every result type, he only wants to see only one row, and that is the Result which has last been added for that type.
I think that, the easiest way to achieve this, would be to create an additional class, which we can call 'MyResultOverview' for instance:
public class MyResultOverview
{
public int ResultId {get; set;}
public int ResultTypeId {get; set;}
public DateTime ResultDate {get; set;}
}
This class should not be mapped, but NHibernate should be aware that this class exists. Therefore, we'll have to import it:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<import class="MyResultOverview" />
</hibernate-mapping>
Then, we can create an ICriteria which will populate instances of MyResultOverview (and which will also generate the most efficient SQL Query in order to get this overview).
It should look something like this:
ICriteria criteria = session.CreateCritera (typeof(MyResult));
criteria.SetProjection (Projections.ProjectionList ()
.Add (Projections.Property("Id"), "ResultId")
.Add (Projections.Property("ResultType"), "ResultType")
.Add (Projections.Max("ResultDate"), "ResultDate"));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(MyResultOverview)));
IList<MyResultOverview> results = criteria.List<MyResultOverview>();
This should give you a list of MyResultOverview instances which represent the MyResults that you're looking for.
Then, in order to retrieve the MyResult itself, you can simply do this by retrieving the MyResult instance for that particalur ResultId that you've retrieved as well.
I haven't tested this, nor did i compile it, but this is the path that I would follow to achieve this.
Order by ResultDate (descending) and select top whatever you feel appropriate.
In HQLthis might work:
select item, tag
from MyItem item
join item.Tags tag
where tag.Id = (
select max(tag2.Id)
from MyItem item2
join item2.Tags tag2
where item2.Id = item.Id
group by item2.Id
)