The code I written actually does it's job but it is however not very optimal.
In my example I have two very simple tables
A simple SQL Query to get the person name and related country name (if available) would look like this
SELECT Person.Name AS Name, Country.CountryName AS Country FROM Person LEFT OUTER JOIN Country ON Country.CountryId = Person.CountryId
And a well constructed object picking up data with NHibernate looks like this
Simple enough, right. My models looks like this
namespace NHibernateLeftJoin.Models
{
public class Country
{
public virtual int CountryId { get; set; }
public virtual string CountryName { get; set; }
}
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Name { get; set; }
public virtual int CountryId { get; set; }
public virtual Country CountryObject { get; set; }
}
}
And the mappings
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
<class name="NHibernateLeftJoin.Models.Country, NHibernateLeftJoin" lazy="true">
<id name="CountryId">
<generator class="native" />
</id>
<property name="CountryName" />
</class>
<class name="NHibernateLeftJoin.Models.Person, NHibernateLeftJoin" lazy="true">
<id name="PersonId">
<generator class="native" />
</id>
<property name="Name" />
<property name="CountryId" />
<many-to-one name="CountryObject" class="NHibernateLeftJoin.Models.Country" lazy="false"
column="CountryId" outer-join="true" unique="true" not-null="false" cascade="none" />
</class>
</hibernate-mapping>
The issue with this approach is that NHibernate fetches one 'Person' row and calls the 'Contry' table for each row, and if we are having thousands of rows that's not very good. It seem to be impossible to perform this in the same neat way as we do with SQL or it's me using the totally wrong approach.
The VS project can be found here for anyone interested https://dl.dropboxusercontent.com/u/6208162/NHibernateLeftJoin.zip
Thanks
== EDIT ==
A query may look like this
private static IList<Person> GetPersons()
{
using (var session = NHibernateHelper.OpenSession())
{
//using (var transaction = session.BeginTransaction()) {}
IQuery query = session.CreateQuery("FROM Person");
return query.List<Person>();
}
}
The sql generated by that will look like this
NHibernate: select person0_.PersonId as PersonId1_, person0_.Name as Name1_, person0_.CountryId as CountryId1_ from Person person0_
NHibernate: SELECT country0_.CountryId as CountryId0_0_, country0_.CountryName as CountryN2_0_0_ FROM Country country0_ WHERE country0_.CountryId=@p0;@p0 = 1 [Type: Int32 (0)]
NHibernate: SELECT country0_.CountryId as CountryId0_0_, country0_.CountryName as CountryN2_0_0_ FROM Country country0_ WHERE country0_.CountryId=@p0;@p0 = 2 [Type: Int32 (0)]
NHibernate: SELECT country0_.CountryId as CountryId0_0_, country0_.CountryName as CountryN2_0_0_ FROM Country country0_ WHERE country0_.CountryId=@p0;@p0 = 3 [Type: Int32 (0)]
This is quite logical with my data, the first query it fetches all Persons and then it fetches the data for the CountryObject with three different queries (there are three unique countries mapped to users).
Thanks
First off, remove this property... public virtual int CountryId { get; set; }
public virtual int CountryId { get; set; }
public virtual int CountryId { get; set; }
You can access the CountryId via... CountryObject.CountryId
. I think what you are looking for is the fetch="join"
on your ManyToOne. Which tells NH, to outer-join to this table EVERY time you query a Person. This would make the ManyToOne non-lazy.
http://nhibernate.info/doc/nh/en/#mapping-declaration-manytoone
Usually, I would avoid this setting and use a QueryOver to manually join or eager fetch any ManyToOnes. If I'm working with a single entity, I'll use the default lazy loading for ManyToOnes.
Session.QueryOver<Person>()
.Fetch(x => x.CountryObject).Eager
.List();
well one way to get around this is to project into a class when you write the query.
A linq query to do something like this might look like this
var people = from p in _session.Query<Person>()
select new
{
Name = p.Name,
Country = p.Country == null ? String.Empty : p.Country.Name;
}
Also you don't need the CountryId on the Person object. You should be able to grab the countryId like this person.Country.Id if a person is associated with a Country.
The solution was to implement the join in the CreateQuery method as follows
IQuery query = session.CreateQuery("FROM Person AS P LEFT JOIN FETCH P.CountryObject");
return query.List<Person>();
And write the mapping file for Person like this
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
<class name="NHibernateLeftJoin.Models.Person, NHibernateLeftJoin" lazy="true">
<id name="PersonId">
<generator class="native" />
</id>
<property name="Name" />
<property name="CountryId" />
<many-to-one name="CountryObject" class="NHibernateLeftJoin.Models.Country"
column="CountryId" not-found="ignore"
cascade="none" />
</class>
</hibernate-mapping>
In my opinion I can't understand why this query isn't automatically written when adding the parameters fetch="join" outer-join="true" to the many-to-one element in the mapping file.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.