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?
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.
I have Entity 'Content'. Each Content has a 'Placement' property. Placement has a many-to-many relationship width 'AdType' entity (Placement has IList<\AdType> property mapped).
I need to load all Placements that are used at least in one Content and associated width specified AdType.
My DAL function looks like this:
public IList<Placement> Load(AdType adType)
{
return NHibernateSession.QueryOver<Content>()
.JoinQueryOver(content => content.Placement)
.JoinQueryOver<AdType>(placement => placement.AdTypes)
.Where(_adType => _adType.Id == adType.Id)
.Select(x => x.Placement).List<Placement>();
}
This works fine but when I look to the SQL log i see:
SELECT this_.PlacementId as y0_ FROM AdManager.dbo.[Content] this_ inner join AdManager.dbo.[Placement] placement1_ on this_.PlacementId=placement1_.PlacementId inner join AdManager.dbo.AdTypeToPlacement adtypes5_ on placement1_.PlacementId=adtypes5_.PlacementId inner join AdManager.dbo.[AdType] adtype2_ on adtypes5_.AdTypeId=adtype2_.AdTypeId WHERE adtype2_.AdTypeId = #p0
SELECT placement0_.PlacementId as Placemen1_26_0_, placement0_.Name as Name26_0_ FROM AdManager.dbo.[Placement] placement0_ WHERE placement0_.PlacementId=#p0
SELECT placement0_.PlacementId as Placemen1_26_0_, placement0_.Name as Name26_0_ FROM AdManager.dbo.[Placement] placement0_ WHERE placement0_.PlacementId=#p0
This means that NHibernate takes all placements Id in first query and then queries all fields from Placement table by Id.
My question is: Does enyone know how to modify QueryOver method to force NHibernate load data in one query?
it seems NHibernate does think there might be something in the where which maybe filters out data which is needed tro initialize the placement. You can go with a subquery:
public IList<Placement> Load(AdType adType)
{
var subquery = QueryOver.For<Content>()
.JoinQueryOver(content => content.Placement)
.JoinQueryOver<AdType>(placement => placement.AdTypes)
.Where(_adType => _adType.Id == adType.Id)
.Select(x => x.Id);
return NHibernateSession.QueryOver<Content>()
.WithSubquery.Where(content => content.Id).IsIn(subquery))
//.Fetch(x => x.Placement).Eager try with and without
.Select(x => x.Placement).List<Placement>();
}
or SQL (has the disadvantage that it just fills the new Placement but doest track it)
public IList<Placement> Load(AdType adType)
{
return NHibernateSession.CreateSQLQuery("SELECT p.Name as Name, ... FROM content c join placement p...")
.SetResultTransformer(Transformers.AliastoBean<Placement>())
.List<Placement>();
}
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 have enabled 2nd level cache in FluentNHibernate:
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(connectionString)
.Cache(c => c.ProviderClass<SysCacheProvider>().UseQueryCache())
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<PersonMap>());
My mapping is as follows:
public PersonMap()
{
Id(x => x.Id);
Map(x => x.Name);
Cache.ReadWrite();
}
When I call Persons from my repository, I run:
var query = session.GetSession().CreateCriteria<Person>("p")
.Add(Expression.Eq("p.Org.Id", orgRep.GetOrg().Id));
query.SetCacheable(true);
return query.List<Person>().AsQueryable<Person>();
When I start the application everything (including cache) works fine. My first query will hit the database, but following ones don't. Problem arises when I save the Person. Person is saved like:
public virtual void Save(Person p)
{
if (p.Id > 0 && session.GetSession().Get<Person>(p.Id).Org != orgRep.GetOrg())
throw new SecurityException("Organization mismatch");
session.GetSession().Merge(p);
session.GetSession().Flush();
}
Saving works but after that the cache doesn't. Queries will always hit the database. Looking through nhibernate log says that:
DEBUG - Checking query spaces for up-to-dateness [[Person]]
DEBUG - Fetching object 'NHibernate-Cache:UpdateTimestampsCache:[Person]#1639794674' from the cache.
DEBUG - cached query results were not up to date for: sql: SELECT this_.Id as Id0_0_, this_.Name as Name0_0_, this_.Org_id as Org5_0_0_ FROM [Person] this_ WHERE this_.Org_id = ?; parameters: ['1']; first row: 0
What am I doing wrong?
Try instantiating an explicit transaction within a session using block. This article looks relevant:- http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions