Fluent nHibernate Getting HasMany Items In Single Query - nhibernate

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.

Related

Fluent NHibernate Child collection persistence issues

I have the following mapping classes (only the relevant part copied):
public class CardTemplateMapping : ClassMap<CardTemplate>
{
public CardTemplateMapping()
{
Table("cardtemplate");
Id(x => x.Id)
.Column("id")
.GeneratedBy.Native();
HasMany(x => x.CostStructures)
.KeyColumn("cardtemplate_id")
.Cascade.All();
}
}
public class CostStructureMapping : ClassMap<CostStructure>
{
public CostStructureMapping()
{
Table("coststructure");
Id(x => x.Id)
.Column("id")
.GeneratedBy.Native();
References(x => x.CardTemplate)
.Column("cardtemplate_id");
HasMany(x => x.CostComponents)
.KeyColumn("coststructure_id")
.Cascade.AllDeleteOrphan();
}
}
public class CostStructureComponentMapping : ClassMap<CostStructureComponent>
{
public CostStructureComponentMapping()
{
Table("CostStructureComponent");
Id(x => x.Id)
.Column("id")
.GeneratedBy.Native();
References(x => x.CostStructure)
.Column("coststructure_id");
References(x => x.ResourceType)
.Column("resourcetype_id");
}
}
Nhibernate loads the relations as I intended. But two parts of the Mapping seem a bit weird (either that or my expectations are weird).
First:
CostStructure structure = new CostStructure() { CardTemplate = template };
template.CostStructures.Add(structure);
Session.Save(template);
Session.Flush();
This does not save the new CostStructure instance. Why?
Second one: Assuming I have loaded a CardTemplate having a CostStructure containing three CostStructureComponent entities. Now I want to remove one of those CostStructureComponents from the CostStructure.
I tried the following :
costStructure.CostComponents.Remove(component);
component.CostStructure = null;
session.Save(template)
Now, I know that explicitly deleting explicitly works, but shouldn't the DeleteOrphan part also assert that the component, now without CostStructure to reference, is deleted? Instead, NHibernate tries to perform the following update:
UPDATE CostStructureComponent
SET amount = #p0,
coststructure_id = #p1,
resourcetype_id = #p2
WHERE id = #p3;
#p0 = 1 [Type: Int32 (0)],
#p1 = NULL [Type: Int64 (0)],
#p2 = 5 [Type: Int64 (0)],
#p3 = 13 [Type: Int64 (0)]
Could it be Equals / GetHashCode have been implemented the wrong way?
Sorry, it's late, been a long day and so forth...If I'm talking gibberish, please let me know...
All the answers are hidden in one setting .Inverse()
public CardTemplateMapping()
{
HasMany(x => x.CostStructures)
.KeyColumn("cardtemplate_id")
.Cascade.All()
.Inverse(); // HERE this setting
And here:
public CostStructureMapping()
{
..
HasMany(x => x.CostComponents)
.KeyColumn("coststructure_id")
.Cascade.AllDeleteOrphan()
.Inverse(); // and HERE and everywhere on HasMany()
Try to find some articles about this setting, but in general: If mapped as inverse, NHibernate is ready to do much better SQL Statements, because it is working with other end of relation...
Some sources:
19.5.2. Lists, maps, idbags and sets are the most efficient collections to update cite:
However, in well-designed NHibernate domain models, we usually see that most collections are in fact one-to-many (comment: HasMany in Fluent) associations with inverse="true". For these associations, the update is handled by the many-to-one (comment: References in Fluent) end of the association, and so considerations of collection update performance simply do not apply.
6.2. Mapping a Collection
inverse (optional - defaults to false) mark this collection as the "inverse" end of a bidirectional association

NHibernate - possible to fetch a collection after querying?

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

NHibernate using wrong table alias

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).

NHibernate paging criteria with fetchmode eager. (using fluent NH)

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>();

Fluent NHibernate mapping a CompositeId and Querying with SetProjection

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?