Question: How to get an eager loaded criteria to return paged results on the root entity with all child collections set fetchmode = eager.
I am trying to get a 10 item paged result set with eager loaded child collections. The problem is the query does a select top 10 wrapped around the entire select. The causes it to return only the first 10 results including all joined records. If the first entity has 10 child objects then my result set will return 1 entity with 10 child objects loaded. I need the entities and child collections returned hydrated (lazy off). If I turn lazy loading off and run this query I get the n+1 query for each associate in result set.
This is my basic query process:
criteria = context.Session.CreateCriteria<Associate>();
criteria.SetMaxResults(10); //hardcoded for testing
criteria.SetFirstResult(1); //hardcoded for testing
criteria.SetFetchMode("Roles", NHibernate.FetchMode.Eager);
criteria.SetFetchMode("Messages", NHibernate.FetchMode.Eager);
criteria.SetFetchMode("DirectReports", NHibernate.FetchMode.Eager);
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
return criteria.List<Associate>();
public AssociateMap()
{
ReadOnly();
Id(x => x.AssociateId);
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.ManagerId);
Map(x => x.Department);
Map(x => x.Email);
Map(x => x.JobTitle);
Map(x => x.LastFirstName).Formula("LTRIM(RTRIM(LastName)) + ', ' + LTRIM(RTRIM(FirstName))");
HasMany(x => x.Messages).KeyColumn("AssociateId").Inverse().Cascade.All();
HasMany(x => x.Roles).Element("RoleKey");
HasMany(x => x.DirectReports).KeyColumn("ManagerId").Cascade.None().ForeignKeyConstraintName("FK_Associate_Manager");
//HasMany(x => x.DirectReports).Element("ManagerId").CollectionType(typeof(Domain.Associate));
}
The solution ended up using a subquery to set the max results. I added the subquery using Subqueries.PropertyIn. I am cloning the "criteria" to "limiter" because I added criterion expression in code not shown. So I need to clone these criterion into the subquery so the top 10 select will be in the "IN" statement. Now I can eager load the child collections and add pagination to the root entity to get 10 enties back without issues with cartesian or n+1. I will try to follow up with more complete and organized code.
//criteria = context.Session.CreateCriteria<Associate>();
//changed criteria to DetachedCriteria.
criteria = DetachedCriteria.For<Associate>();
DetachedCriteria limiter = CriteriaTransformer.Clone(criteria);
limiter.SetProjection(Projections.Id());
limiter.SetMaxResults(10);
criteria.Add(Subqueries.PropertyIn("AssociateId", limiter));
criteria.SetFetchMode("Roles", NHibernate.FetchMode.Eager);
criteria.SetFetchMode("Messages", NHibernate.FetchMode.Eager);
criteria.SetFetchMode("DirectReports", NHibernate.FetchMode.Eager);
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
return criteria.List<Associate>();
Related
I have a Topic map which has many posts in it i.e… (At the bottom HasMany(x => x.Posts))
public TopicMap()
{
Cache.ReadWrite().IncludeAll();
Id(x => x.Id);
Map(x => x.Name);
*lots of other normal maps*
References(x => x.Category).Column("Category_Id");
References(x => x.User).Column("MembershipUser_Id");
References(x => x.LastPost).Column("Post_Id").Nullable();
HasMany(x => x.Posts)
.Cascade.AllDeleteOrphan().KeyColumn("Topic_Id")
.Inverse();
*And a few other HasManys*
}
I have written a query which gets the latest paged topics, loops through and displays data and some posts data (Like the count of child posts etc..) . Here is the query
public PagedList<Topic> GetRecentTopics(int pageIndex, int pageSize, int amountToTake)
{
// Get a delayed row count
var rowCount = Session.QueryOver<Topic>()
.Select(Projections.RowCount())
.Cacheable().CacheMode(CacheMode.Normal)
.FutureValue<int>();
var results = Session.QueryOver<Topic>()
.OrderBy(x => x.CreateDate).Desc
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.Cacheable().CacheMode(CacheMode.Normal)
.Future<Topic>().ToList();
var total = rowCount.Value;
if (total > amountToTake)
{
total = amountToTake;
}
// Return a paged list
return new PagedList<Topic>(results, pageIndex, pageSize, total);
}
When I use SQLProfiler on this, as I loop over the topics is does a db hit to grab all Posts from the parent topic. So if I have 10 topics, I get 10 DB hits as it grabs the posts.
Can I change this query to grab the posts as well in a single query? I guess some sort of Join?
You can define eager fetching using Fetch.xxx on your HasMany property mapping. Available options are Fetch.Join(), Fetch.Select() and Fetch.SubSelect(). More info on each type of fetching can be found on NHibernate's documentation.
HasMany(x => x.Posts)
.Cascade.AllDeleteOrphan().KeyColumn("Topic_Id")
.Fetch.Join()
.Inverse();
In my opinion, the best way is defining a reasonable batch-size for the collection (rule of thumb: your default parent page size)
That way, after getting the parent items, you'll get a single query for each child collection type you iterate.
There's sooo much literature about fetching and eager loading when doing the actual query using .Fetch
But, once I have a loaded entity - with an empty collection (because I chose not to eager load at query time due to the cartesian product side-effect), can I choose to load a collection a bit later on, say after I've done some paging and I have a concrete List of items?
something like:
var list = (some linq over Session.Query<Entity>)
.Take(10).Skip(2)
.Fetch(x => x.MyCollection)
.ToList();
Session.Fetch<Entity>(list, l => l.OtherCollection);
Edit
The point is - i'm already fetching 2 child collections in the Query - makes the query and result set quite sizeable already (see nhibernate Cartesian product). I'd like page the results, get a list of 10 then optionally go back to the database to populate child collection properties of the paged (10, say) result. This is a performance consideration.
Issue this query
/*we dont need the result*/Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.OtherCollection)
.ToList();
then nhibernate should initialize the collections on the Entities
EDIT:
to improve initial loading time see http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate
then you can do for exmaple
var results = (some linq over Session.Query<Entity>)
.Take(10).Skip(2)
.ToList();
var q = Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.MyCollection)
.ToFuture();
Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.OtherCollection)
.ToFuture();
Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.ThirdCollection)
.ToFuture();
return q.ToList()
I've just gone off to read about futures and projections in NHibernate which look promising as a solution...will post more when I find out about it.
This is a solution: http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate, but I still do not like it, as my query itself is quite expensive (uses '%like%# + paging), so executing 3 or 4 times just to load collections seems expensive
Edit
This is what I have. Lookign at the sql generated, the correct sql is being run and returning expected results, but the collections on the returned results are null. Can you see what's missing?:
public List<Company> CompaniesForLoggedInUser(int pageSize, int pageNumber)
{
var list =
QueryForCompaniesFor(SecurityHelper.LoggedInUsername)
.Page(pageNumber, pageSize)
.ToList()
.FetchCompanyCollections(Session);
return list;
}
internal static class CompanyListExtensions
{
internal static List<Company> FetchCompanyCollections(this List<Company> companies, ISession session)
{
var ids = companies.Select(l => l.Id).ToArray();
session.QueryOver<Company>()
.Where(x => x.Id.IsIn(ids))
.Fetch(l => l.Properties).Eager()
.Future();
return session.QueryOver<Company>()
.Where(x => x.Id.IsIn(ids))
.Fetch(l => l.UserAccessList).Eager()
.Future()
.ToList();
}
}
I am trying to filter a collection based on a foreign key. I have two classes which are mapped with
public class GroupPriceOverrideMap:ClassMap<GroupPriceOverride>
{
public GroupPriceOverrideMap()
{
CompositeId()
.KeyReference(x => x.Service,"ServiceCode")
.KeyReference(x => x.CustomerAssetGroup, "GroupID");
Map(x => x.Price);
Table("accGroupPriceOverride");
}
}
public class CustomerAssetGroupMap:ClassMap<CustomerAssetGroup>
{
public CustomerAssetGroupMap()
{
Id(x => x.GroupID).Unique();
Map(x => x.Description);
References(x => x.Customer).Column("CustomerID");
HasMany<GroupPriceOverride>(x => x.PriceOverrides).KeyColumn("GroupID");
Table("accCustAssetGroup");
}
}
I query it using
_session.Linq<GroupPriceOverride>.Where(x => x.CustomerAssetGroup.GroupID == groupID)
However this is generating
SELECT this_.ServiceCode as ServiceC1_9_0_, this_.GroupID as GroupID9_0_, this_.Price as Price9_0_ FROM accGroupPriceOverride this_ WHERE customeras1_.GroupID = #p0
there where clause is referencing a table alias which doesn't exist(customeras1). This is probably an alias for crossing with customerassetgroup but there is no need to perform that cross. I'm sure that it is just something in my mapping with is wrong but I can't find it. I've tried various column renaming in case the presence of GroupID in both tables was causing problems but that didn't fix it. Any ideas?
Edit
I found that if I queried doing
_session.Linq<CustomerAssetGroup>().Where(x => x.GroupID == groupID).FirstOrDefault().PriceOverrides;
then I got the correct result. I also found that if I saved a GroupPriceOverride and then queried for it using HQL then it wouldn't be found but I could still find the entity by loading the parent and looking at its collection of overrides.
_session.CreateQuery("FROM GroupPriceOverride i").List().Count;//returns 0
_session.CreateQuery("FROM CustomerAssetGroupi").List().FirstOrDefault().PriceOverrides.Count;//returns 1
Looks like a bug in the old LINQ provider. Could you file a bug here:
https://nhibernate.jira.com/secure/Dashboard.jspa
You might be able to get around it via:
_session.Linq<GroupPriceOverride>.Where(x => x.CustomerAssetGroup == group)
and let NHibernate figure out the ID. If you don't have the group already, you could do this:
var group = _session.Load<CustomerAssetGroup>(groupID);
_session.Linq<GroupPriceOverride>.Where(x => x.CustomerAssetGroup == group)
The ISession.Load(id) will only generate a proxy, but won't actually hit the database until you access a property (which you wouldn't be since you're just using it to specify the ID).
I am trying to retrieve the individual detail rows without having to create an object for the parent. I have a map which joins a parent table with the detail to achieve this:
Table("UdfTemplate");
Id(x => x.Id, "Template_Id");
Map(x => x.FieldCode, "Field_Code");
Map(x => x.ClientId, "Client_Id");
Join("UdfFields", join =>
{
join.KeyColumn("Template_Id");
join.Map(x => x.Name, "COLUMN_NAME");
join.Map(x => x.Label, "DISPLAY_NAME");
join.Map(x => x.IsRequired, "MANDATORY_FLAG")
.CustomType<YesNoType>();
join.Map(x => x.MaxLength, "DATA_LENGTH");
join.Map(x => x.Scale, "DATA_SCALE");
join.Map(x => x.Precision, "DATA_PRECISION");
join.Map(x => x.MinValue, "MIN_VALUE");
join.Map(x => x.MaxValue, "MAX_VALUE");
});
When I run the query in NH using:
Session.CreateCriteria(typeof(UserDefinedField))
.Add(Restrictions.Eq("FieldCode", code)).List<UserDefinedField>();
I get back the first row three times as opposed to the three individual rows it should return. Looking at the SQL trace in NH Profiler the query appears to be correct. The problem feels like it is in the mapping but I am unsure how to troubleshoot that process. I am about to turn on logging to see what I can find but I thought I would post here in case someone with experience mapping joins knows where I am going wrong.
Found aboutn about SetResults transformer here :
http://www.coderanch.com/t/216546/ORM/java/Hibernate-returns-duplicate-results
Which makes your code:
Session.CreateCriteria(typeof(UserDefinedField))
.Add(Restrictions.Eq("FieldCode", code))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity)
.List<UserDefinedField>();
Cheers
John
I have two tables (Section and SectionList) that are related by a many to many table (Membership). Since there is an extra property in the many to many table, i have to break it out into its own entity:
public MembershipMap()
{
UseCompositeId()
.WithKeyReference(x => x.Section, "SectionId")
.WithKeyReference(x => x.SectionList, "SectionList");
Map(x => x.Status);
}
And SectionList is mapped as follows:
public SectionListMap()
{
Id(x => x.Id)
.WithUnsavedValue(0);
HasMany(x => x.Memberships)
.Inverse()
.Cascade.AllDeleteOrphan();
}
The relationship seems to work fine, except for when I try to run advanced queries on it. For instance, here is a query that grabs only certain fields and transforms to a DTO:
var criteria = DetachedCriteria.For<CustomSectionListMembership>()
.CreateAlias("SectionList", "sl")
.CreateAlias("Section", "s")
.CreateAlias("s.Website", "w")
.CreateAlias("w.Publisher", "p")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("s.Id"), "SectionId") //add projections for every propery you want returned
.Add(Projections.Property("s.Name"), "SectionName") // mapping entity name -> DTO name
.Add(Projections.Property("w.Id"), "WebsiteId")
.Add(Projections.Property("w.Name"), "WebsiteName")
.Add(Projections.Property("p.Id"), "PublisherId")
.Add(Projections.Property("p.Name"), "PublisherName")
.Add(Projections.Property("Status")))
.Add(Expression.Eq("sl.Id", listId))
.SetResultTransformer(Transformers.AliasToBean(typeof(MembershipDTO))); //transform to the DTO
var membership = repository.FindAll(criteria);
This query errors out with "could not execute query", because the query being generated is completely missing the inner joins that should have been generated by the CreateAlias calls:
SELECT s2_.SectionId as y0_,
s2_.Name as y1_,
w3_.WebsiteId as y2_,
w3_.Name as y3_,
p4_.PublisherId as y4_,
p4_.Name as y5_,
this_.Status as y6_ FROM Membership this_ WHERE csl1_.ListId = 6923 /* #p0 */
What could possibly be the problem?