NHibernate many-to-one with lazy false ever executes N queries - nhibernate

I want a list of Purchase searched with some criteria.
Purchase entity contains one Product.
This is the mappings:
<class name="Product" table="Products">
<id name="Id">
<generator class="identity"></generator>
</id>
<property name="Name"></property>
<many-to-one name="Brand" column="BrandId" lazy="false" fetch="join" />
<many-to-one name="Category" column="CategoryId" lazy="false" fetch="join" />
</class>
<class name="Shop" table="Shops">
<id name="Id">
<generator class="identity"></generator>
</id>
<property name="Name"></property>
</class>
<class name="Purchase" table="Purchases" lazy="false">
<id name="Id">
<generator class="identity"></generator>
</id>
<many-to-one name="Product" column="ProductId" lazy="false" fetch="join" />
<many-to-one name="Shop" column="ShopId" lazy="false" fetch="join" />
</class>
And this is the way as I perform the search:
string query = string.Format(#"
select pc from Purchase as pc
inner join pc.Product as p
left outer join p.Brand as b
where
{0} and {1}
and
(
(p.Name like '%{2}%')
or (b.Name like '%{2}%')
)
",
filters.From.HasValue ? "pc.Date >= " + SqlHelper.GetSqlDate(filters.From) : "1=1",
filters.To.HasValue ? "pc.Date <= " + SqlHelper.GetSqlDate(filters.To) : "1=1",
filters.Text);
var list = session.CreateQuery(query)
.List<Purchase>(); //.Future<Purchase>().ToList();
return list;
(I didn't remove other filters on query just to "show" that a simple Get/Query/QueryOver is not possible (it is not readable as a SQL or HQL)
It can be improved... but the problem here is that this code produce 1 query for purchase list and N queries for Product plus M queries for Shop, for example 251 or 770 queries was executed instead of one.
I aspect that just one query was executed, because of lazy="false" on the many-to-one relation.
Why NHibernate make a query on Product (and on Shop) for every Purchase?
Ho can I change the mapping to obtain the execution of one query?
Thanks,
Alessandro

HQL does not respect fetch="join" (in the mapping). I explicit it adding fetch in the join:
select pc from Purchase as pc
inner join **fetch** pc.Product as p
left outer join **fetch** p.Brand as b
... and it works: only one query is executed.

Related

SELECT statement based on OneToMany relation and dates

Overview:
I have these beans in my hbm files. First is a report with two dates (start and stop of report) and a list of excluded periods. Second is keeping excluded periods for report. Report is turned off during these excluded periods.
<class name="com.company.Report" table="TReport">
<property name="startDate" column="m_StartDate" type="timestamp"/>
<property name="endDate" column="m_EndDate" type="timestamp"/>
<list name="excludedPeriods" table="TExcludedPeriod" inverse="true" lazy="false">
<key column="m_TReport" not-null="true"/>
<list-index column="m_ListIndex" base="1"/>
<one-to-many class="com.company.ExcludedPeriod"/>
</list>
</class>
<class name="com.company.ExcludedPeriod" table="TExcludedPeriod">
<id column="m_Id" name="id" />
<many-to-one name="report" class="com.company.JobsReport" column="m_TJobReport" not-null="true" />
<property name="index" column="m_ListIndex" type="integer"/>
<property name="start" column="m_Start" type="timestamp"/>
<property name="stop" column="m_Stop" type="timestamp"/>
</class>
Task:
Select all reports that are turned on during a selected period of time (defined by startPeriod and endPeriod).
My code:
SELECT * FROM treport r LEFT OUTER JOIN texcludedperiod ep ON r.m_id=ep.m_treport
WHERE NOT (ep.m_id IS NOT NULL AND (ep.m_start>=startPeriod AND ep.m_end<=endPeriod));
Problem:
It is working if there is no excluded period for a report or there is only one such a period, but in other cases it selects reports that shouldn't be selected. Consider this example (d/m/yyyy format):
Report: start 1/1/2000, stop 1/2/2000, excluded periods:
7/1/2000-10/1/2000, 12/1/2000-15/1/2000
Searched period: startPeriod 8/1/2000, stopPeriod 9/1/2000.
This report shouldn't be selected.
This is one of those cases where you should use a NOT EXISTS:
SELECT *
FROM treport r
WHERE NOT EXISTS (SELECT *
FROM texcludedperiod ep
WHERE ep.m_treport = r.m_id AND
ep.m_start >= startPeriod AND
ep.m_end <= endPeriod);
Basically you're saying "Give me all the reports where there are no exclusions during the reporting period in question".

NHibernate inner join gives "Path expected for join"

I have three Tables:
- Person
- User
- PersonSecret
where PersonSecret reference to Person and User:
<class name="PersonSecret" table="PersonSecret" lazy="false" >
<id name="Id" column="Id" type="Guid">
<generator class="assigned"/>
</id>
...
<many-to-one name="Person" class="Person" foreign-key="FK_Person_PersonSecret" lazy="proxy" fetch="select">
<column name="PersonId"/>
</many-to-one>
<many-to-one name="User" class="User" foreign-key="FK_User_PersonSecret" lazy="proxy" fetch="select">
<column name="UserId"/>
</many-to-one>
This is the mapping from User to PersonSecret:
<set name="PersonSecrets" lazy="true" inverse="true" cascade="save-update" >
<key>
<column name="UserId"/>
</key>
<one-to-many class="PersonSecret"/>
And this from Person to PersonSecret:
<set name="PersonSecrets" lazy="true" inverse="true" cascade="save-update" >
<key>
<column name="PersonId"/>
</key>
<one-to-many class="PersonSecret"/>
Now, i try to select all Persons, which has a Entry in PersonSecret for a specific User:
var query = this.Session.CreateQuery(#"from Person a inner join PersonSecret b
where b.UserId = :userId and b.RemindeBirthday = :remind");
This gives me now the ExceptionMessage: "Path expected for join"
Can someone help me, what I am doing wrong? - Thanks.
There are a couple of issues with your HQL query:
When using HQL you need to reference the names of the properties on your model classes instead of the column names they map to. In this case you should reference the PersonSecret.User.Id property in your where clause, instead of the UserId column.
You should also specify the Person.PersonSecrets property in the join clause, otherwise NHibernate won't know which columns to join on.
You should specify your additional filter as a join condition using the with keyword instead of in the where clause.
Here's the correct version:
var query = this.Session.CreateQuery(
#"from Person as a inner join a.PersonSecrets as b with b.User.Id = :userId and b.RemindeBirthday = :remind");
Related resources:
HQL: The Hibernate Query Language - Associations and joins
you have forgotten the "ON" term.
inner join PersonSecret b ON b.PersonId = a.PersonId

One-to-many NHibernate

hello
I have mapping like this :
<class entity-name="Person">
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<property name="FirstName" column="FIRST_NAME" type="string"/>
<property name="LastName" column="LAST_NAME" type="string"/>
<bag name="Addresses" inverse="true" lazy="false" cascade="all"> <key column="Person_ID"/>
<one-to-many class="Address"/> </bag>
<class entity-name="Address">
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<property name="City" column="City" type="string"/>
<property name="Country" column="Country" type="string"/>
<property name="PersonId" column="Person_ID" type="long"/>
</class>
I need fetch all persons that live in Paris. For this I use query like
select p from Person p inner join Address a on p.Id=a.PersonId where a.City like 'Paris'
And it's O.k.
But Nhbernate Executes another one query , select a from Address a where a.PersonId in (all ids of persons that live in paris)
but it's unnecessary , mountains NHibernate can get all fields of Address from join(first query)
Can I Prevent running the second query and get all needed information from first query ????
Yes you can, you just need to specify a fetch. This will initialize the addresses collection.
http://www.nhforge.org/doc/nh/en/index.html#queryhql-joins
select p from Person p inner join fetch
Address a on p.Id=a.PersonId where a.City like 'Paris'

Multiple many-to-one to a single table

i have a Product table with two many-to-one references (Title & Description) to a single table named TextRef :
Product:
<many-to-one lazy="false" name="Title" class="TextRef" column="TitleRef" fetch="join" cascade="save-update"/>
<many-to-one lazy="false" name="Description" class="TextRef" column="DescriptionRef" fetch="join" cascade="save-update"/>
every TextRef has a one-to-many to TextRefItem table :
<bag lazy="false" name="Values" table="TextRefItem" cascade="save-update" inverse="true" fetch="join">
<key column="TextId"></key>
<one-to-many class="TextRefItem"/>
</bag>
now i want to load all of TextRefItem(s) for Title & Description in one go but NHibernate only create a join with the first reference (Title)
SELECT this_.ProductId as ProductId7_2_, this_.CategoryId as CategoryId7_2_,
this_.DescriptionRef as Descript3_7_2_,
textref2_.TextId as TextId8_0_, values3_.TextId as TextId4_,
values3_.TextItemId as TextItemId4_, values3_.TextItemId as TextItemId9_1_,values3_.LangId as LangId9_1_,
values3_.Text as Text9_1_, values3_.TextId as TextId9_1_
FROM
Product this_
inner join TextRef textref2_ on this_.DescriptionRef=textref2_.TextId
left outer join TextRefItem values3_ on textref2_.TextId=values3_.TextId
WHERE this_.ProductId = 1
for the other one(Description) it makes a separate select query
how could i tell NHibernate to avoid that ?
I don't know why a separate query is made for the second reference (Description). But it is possible to extend fetching strategies via criterias to load a product with all TextRefs and TextRefItems in one shot:
var criteria = session.CreateCriteria(typeof(Product))
.SetFetchMode("Description.Values", FetchMode.Join);
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
criteria.Add(Restrictions.Eq("ProdId", 1));
var list = criteria.List<Product>();

NHibernate Criteria for a non-linked table

We are adding an attribute(tags) to a system. The attribute table is just a relation table without any foreign keys.
Is there a way to add to the criteria to generate a where clause for the attribute table on a parent table.
<class name="Account" table="dbo.Account" lazy="true" >
<id name="Id" column="`AccountId`">
<generator class="int"/>
</id>
<property name="Name" column="`Name`" />
<property name="Address" column="`Address`" />
</class>
<class name="Attribute" table="dbo.Attribute" lazy="true" >
<id name="Id" column="`AttributeId`">
<generator class="int"/>
</id>
<property name="Name" column="`Name`" />
<property name="LinkId" column="`LinkId`" />
<property name="Type" column="`Type`" />
</class>
Example Data
Account
1 - Person - Address
2 - Person - Address
Attribute
1 - Attrib1 - 1 - Account
2 - Attrib2 - 1 - Account
3 - Attrib1 - 2 - Account
4 - Attrib1 - 3 - Event
5 - Attrib1 - 4 - Location
Sample of Existing Code
ICriteria crit = session.CreateCriteria(typeof(Account));
crit.Add(Restrictions.Eq("Name", "Some"););
I would like to add the following to the where clause.
AccountId IN (SELECT LinkId FROM Attribute WHERE Name = 'Attrib1')
Why aren't you using an any type mapping for this? it does exactly this: referencing by a primary key and type name...
There might be some mistakes in the following piece of code, but it looks something like this:
DetachedCriteria subquery = DetachedCriteria.For<Attribute>()
.Add(Projections.Property("LinkId"))
.Add(Restrictions.Eq("Name", "Attrib1"))
.Add(Restrictions.Eq("Type", typeof(Account)));
ICriteria crit = session.CreateCriteria(typeof(Account));
.Add(Restrictions.Eq("Name", "Some"))
.Add(Subqueries.PropertyIn("id", subquery));