I want to load root entities and eager load all it's child collection and aggregate members.
Have been trying to use the SetFetchMode in FluentNHibernate, but I am getting duplicates in one of the child collection since I have a depth of 3 levels. DistinctRootEntityResultTransformer unfortunately only removes the root duplications.
return Session.CreateInvoiceBaseCriteria(query, archived)
.AddOrder(new Order(query.Order, query.OrderType == OrderType.ASC))
.SetFetchMode("States", FetchMode.Eager)
.SetFetchMode("Attestations", FetchMode.Eager)
.SetFetchMode("AttestationRequests", FetchMode.Eager)
.SetFetchMode("AttestationRequests.Reminders", FetchMode.Eager)
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.List<Invoice>();
Could I use multi queries or something similar to archive this?
Furthermore, wouldn't this approach result in unnecessarily huge result sets from the database?
Any suggestions?
Found a solution but it isn't pretty. First I go and find all the Invoice IDs, then I use them in the multiquery and then at the end filtering the results through a HashedSet. Because of the large number of items sometimes i couldn't use the normalt Restriction.In and was forced to send it as a string.
Any suggested tweaks?
var criteria = Session.CreateInvoiceBaseCriteria(query, archived)
.SetProjection(Projections.Id());
var invoiceIds = criteria.List<int>();
if (invoiceIds.Count > 0)
{
var joinedIds = JoinIDs(criteria.List<int>()); // To many ids to send them as parameters.
var sql1 = string.Format("from Invoice i inner join fetch i.States where i.InvoiceID in ({0}) order by i.{1} {2}", joinedIds, query.Order, query.OrderType.ToString());
var sql2 = string.Format("from Invoice i inner join fetch i.AttestationRequests where i.InvoiceID in ({0})", joinedIds);
var sql3 = string.Format("from Invoice i inner join fetch i.Attestations where i.InvoiceID in ({0})", joinedIds);
var invoiceQuery = Session.CreateMultiQuery()
.Add(sql1)
.Add(sql2)
.Add(sql3);
var result = invoiceQuery.List()[0];
return new UniqueFilter<Invoice>((ICollection)result);
}
return new List<Invoice>();
To answer your question: yes, it results in huge resultsets.
I suggest to:
just naively write your queries without eager fetching
On certain places, put an eager fetch, but only one per query
if you really get performance problems which you can't solve with indexes or by enhance queries and mapping strategies, use your solution with the multiple queries.
While it might not be exactly what you are looking for, I would recommend looking at this article:
Eager loading aggregate with many child collections
If you look around the rest of that site you will find even more posts that discuss eager loading and other great nHibernate stuff.
Related
If you see the documentation for performance of the collections :
http://nhibernate.info/doc/nhibernate-reference/performance.html#performance-collections-taxonomy
It says:
Bags are the worst case. Since a bag permits duplicate element values and has no index column, no primary key may be defined. NHibernate has no way of distinguishing between duplicate rows. NHibernate resolves this problem by completely removing (in a single DELETE) and recreating the collection whenever it changes. This might be very inefficient.
However I cannot confirm this case. For example if I have a simple parent child relation with cascade all, using bag, with the following code:
using (var sf = NHibernateHelper.SessionFactory)
using (var session = sf.OpenSession())
{
var trx = session.BeginTransaction();
var par = session.Query<Parent>().First();
var c = new Child { Id = 4, Name = "Child4" };
par.Children.Add(c);
trx.Commit();
}
I don't see any deletes, but an insert to child table and an update for parentid. This actually make sense. However it seems to contradict with the docs. What am I missing?
The example you give is almost exactly like the efficient case documented in the NHibernate reference at 19.5.3. Bags and lists are the most efficient inverse collections.
In my object graph, VendorServiceRateChange has a lazy loaded property IList<VendorService> VendorServiceList and the VendorService has a lazy loaded property IList<ClientService>.
When I run the following code I get a Cartesian product between my VendorServiceList and my ClientServiceList.
queueList = session
.CreateCriteria<VendorRateChange>()
.Add(Restrictions.IsNull("ProcessedDate"))
.Add(Restrictions.Le("EffectiveDate", DateTime.Now))
.SetFetchMode("VendorServiceList", FetchMode.Join)
.SetFetchMode("VendorServiceList.Vendor", FetchMode.Join)
.SetFetchMode("VendorServiceList.CityService", FetchMode.Join)
.SetFetchMode("VendorServiceList.ClientServices", FetchMode.Join)
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.AddOrder(new Order("Audit.CreatedDate", true))
.List<VendorRateChange>();
Is there a way to structure this query using Criteria or DetachedCriteria that would not result in a Cartesian product as my VendorServiceList? I do not want to have to resort to commenting out the fetch on "VendorServiceList.ClientServices" and looping through with an Initialize call after the initial query has come back.
Thanks in advance.
Are you sure it is a cartesian product, or you just receive "duplicated" rows in the retuned list ?
If so, before .List(), try to call .SetResultTransformer(Transformers.DistinctRootEntity), because by eager fetching the associated collection you receive a list containing duplicates of the same entity.
You can have a single FetchMode.Join before you start creating cartesian products. What if you switch others to FetchMode.Select or something else?
I've a problem with this linq to nhibernate query
var listeShopping = (from cart in session.Query<Cart>()
.Fetch(cart => cart.ItemShopping)
.ThenFetch(item => item.Manufacturer)
select cart.ItemShopping).ToList<ItemShopping>();
When I launch it I've a strange error :
Query specified join fetching, but the owner of the fetched association was
not present in the select list [FromElement{explicit,not a collection join,
fetch join,fetch non-lazy properties,classAlias=_1,role=,tableName= (...)
I need the eager loading, how can I avoid that error ? If it can help, I'll mention that I use the cart table only like a inner join table. I just need to know the ItemShopping in the Cart.
Regards
Edit
I modified the code to make it readable in english. I corrected the error.
Edit 2
I found that method, it seems to work ... Can someone check it if I didn't make an error ?
var list = (from item in session.Query<ItemShopping>()
.Fetch(item => item.Manufacturer)
from cart in item.Cart
select item).ToList<ItemShopping>();
I don't think it's possible to handle this scenario with Linq. But it is with HQL:
var listeShopping = session.CreateQuery(#"
select item
from Cart cart
join cart.ItemShopping item
join fetch item.Manufacturer
")
.List<ItemShopping>();
Side note: eager fetching the Manufacturer this way is not necessarily the best performing approach. Consider using batch-size in the Manufacturer class instead.
I have this
using (ITransaction transaction = session.BeginTransaction())
{
Task tAlias = null;
CompletedTask cAlias = null;
List<Task> tasks = session.QueryOver<Task>(() => tAlias)
.Where(Restrictions.In(Projections.Property(() => tAlias.Course.Id), courseIds))
.Fetch(pt => pt.PersonalTaskReminders).Eager
.List<Task>().ToList().ConvertToLocalTime(student);
transaction.Commit();
return tasks;
}
PersonalTaskReminders == Collection
So a task can have many personalTaskReminders. I am finding though if I set 2 personalTaskReminders(so PersonalTaskReminders will now have 2 rows in it's collection from the db)
That it returns the same task twice.
So if I had 50 personaltaskReminders for that task. I would get 50 results of the same task. I don't understand why.
If I remove the eager loading. I get the one task back from the database as I expected.
It is obvious, because eager fetch causes join with 2 tables. To get rid of duplicated results you should use DistinctRootEntityTransformer.
By the way, NHibernate offers much nicer syntax for IN clause. So your query should look like this:
var tasks = Session.QueryOver<Task>()
.WhereRestrictionOn(x => x.Id).IsIn(courseIds)
.Fetch(pt => pt.PersonalTaskReminders).Eager
.TransformUsing(Transformers.DistinctRootEntity)
.List<Task>();
Xelibrion's solution is the right way to fix the problem.
To understand why the results are duplicated when doing Fetch, you can compare the generated SQL:
Without Fetch the fields in the SELECT are just your root entity Task fields.
With Fetch the fields of PersonalReminder entities have been added to the SELECT. So if you have two PersonalReminder registers for the same Task you get two registers in the results, and a DISTINCT clause would not remove them (as the real returned registers are different because they contain the PersonalReminder fields).
The SQL generated with and without TransformUsing is exactly the same, but NH processes the returned registers to remove the duplicates.
I was looking at this
Be careful not to eagerly fetch
multiple collection properties at the
same time. Although this statement
will work fine:
var employees =
session.Query()
.Fetch(e => e.Subordinates)
.Fetch(e => e.Orders).ToList();
I need to fetch 2 references so I would need to do something like that. Is there a better way of doing this.
I can't do .ThenFetchMany() as it goes to the into the child objects but the ones I am after on the same level.
Well, the query will still return the results you want, but as stated, it will return a cartesian product, i.e. the SQL query will return count(e.Subordinates) * count(e.Orders) results, which could add up pretty quickly, especially if you have more than just two collections.
NHibernate introduced Futures with the 2.1 release. Unfortunately there seems to be no way in the current NHibernate 3.0 release to make them work with NHibernate.Linq (session.Query<T>()).
Futures allow you to perform multiple queries in one roundtrip to the database (as long as the DB supports it, but most do). In that case you will only have count(e.Subordinates) + count(e.Orders) results, which is obviously the minimum.
Futures work with the criteria API, HQL and they are supposed to work with the new QueryOver API (I have not tested that, yet).
NHibernate.Linq does have Query().ToFuture() and Query().ToFutureValue(), but so far I only get Exceptions when I use them.
Edit:
I just checked again for the Linq API and it seems as if it is working if you do not use Fetch. The following will result in three SQL queries that are executed in one roundtrip. The total number of rows return will be 1 + count(Subordinates) + count(Orders).
int id = 1;
// get the Employee with the id defined above
var employee = repo.Session.Query<Employee>()
.Where(o => o.Id == id)
.ToFuture<Employee>();
// get the Subordinates (these are other employees?)
var subordinates = repo.Session.Query<Employee>()
.Where(o => o.HeadEmployee.Id == id)
.ToFuture<Employee>();
// get the Orders for the employee
var orders = repo.Session.Query<Order>()
.Where(o => o.Employee.Id == id)
.ToFuture<Order>();
// execute all three queries in one roundtrip
var list = employee.ToList();
// get the first (and only) Employee in the list, NHibernate will have populated the Subordinates and Orders
Employee empl = list.FirstOrDefault();
Thank you for having marked this as the answer anyway.