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
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.
//Map for Url class
this.Table("Urls");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.Address).Access.BackingField();
this.HasMany(x => x.Parameters)
.Inverse()
.AsList(col => col.Column("ParameterIndex"))
.Cascade.AllDeleteOrphan()
.Access.BackingField();
//Map for UrlParameter class
this.Table("UrlParameters");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.References(x => x.Url).Column("Url").Access.BackingField();
this.Map(x => x.Name).Access.BackingField();
//Code to execute the query
Session.Query<Url>().FetchMany(x => x.Parameters);
//The SQL generated
select url0_.ID as ID1_0_, parameters1_.ID as ID2_1_, url0_.Address as Address1_0_, url0_.Prohibited as Prohibited1_0_, url0_.CreatedOn as CreatedOn1_0_, url0_.LastUpdatedOn as LastUpda5_1_0_, url0_.TotalComments as TotalCom6_1_0_, url0_.TotalNegative as TotalNeg7_1_0_, url0_.TotalNeutral as TotalNeu8_1_0_, url0_.TotalPositive as TotalPos9_1_0_, url0_.CreatedBy as CreatedBy1_0_, parameters1_.Name as Name2_1_, parameters1_.Url as Url2_1_, parameters1_.Url_id as Url4_0__, parameters1_.ID as ID0__, parameters1_.ParameterIndex as Paramete5_0__ from Urls url0_ left outer join UrlParameters parameters1_ on url0_.ID=parameters1_.Url_id where url0_.Address=#p0
//Exception message
Message=Invalid column name 'Url_id'.
Why is NHibernate generating a column name "Url_id" when I have explicitly told it to use "Url" in my UrlParameter mapping?
You need to define the KeyColumn() column in the HasMany mapping.
It should match what you wrote on the References() side ("Url").
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).
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>();
I'm trying to load all the collections eagerly, using NHibernate 3 alpha 1. I'm wondering if this the right way of using ThenFetch()?
Properties with plural names are collections. The others are just a single object.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi).Fetch(mi => mi.Milestone)
.ThenFetch(m => m.PrimaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.SecondaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Predecessors)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Function)
.Fetch(mi => mi.Milestone)
.ThenFetchMany(m => m.Jobs)
.ThenFetch(j => j.Source)
;
I thought of asking this in the NHibernate forums but unfortunately access to google groups is forbidden from where I am. I know Fabio is here, so maybe the guys from the NHibernate team can shed some light on this?
Thanks
Apparently, there's no "right" way to use ThenFetch in such a case. Your example works fine but SQL produced contains many joins to Milestone, which isn't that right.
Using IQueryOver instead of IQueryable allows you to use complex syntax in Fetch:
Fetch(p => p.B)
Fetch(p => p.B.C) // if B is not a collection ... or
Fetch(p => p.B[0].C) // if B is a collection ... or
Fetch(p => p.B.First().C) // if B is an IEnumerable (using .First() extension method)
So in your case it would be:
query // = session.QueryOver<X>()
.Fetch(mi => mi.Milestone).Eager
.Fetch(mi => mi.Milestone.PrimaryOwners).Eager
.Fetch(mi => mi.Milestone.SecondaryOwners).Eager
.Fetch(mi => mi.Milestone.Predecessors).Eager
.Fetch(mi => mi.Milestone.Function).Eager
.Fetch(mi => mi.Milestone.Jobs).Eager
.Fetch(mi => mi.Milestone.Jobs.First().Source).Eager
The one thing you are missing is that you should use FetchMany() and ThenFetchMany() is the child property is a collection.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi);
var fetch = milestoneInstances.Fetch(f => f.Milestone);
fetch.ThenFetch(f => f.PrimaryOwners);
fetch.ThenFetch(f => f.SecondaryOwners);
//...
As leora said, make sure when fetching children collections that you use
FetchMany()
ThenFetchMany()
A new Fetch, should pick up from the root, but this does not always happen. Sometimes you need to create them as separate queries or use Criteria Futures to batch up a multiple fetch.