i'm using nHibernate 2.1.2 and relized that nhibernate will generate left outer join on nested many-to-one entities. it seems start generate left-outer-join on 3rd nested note onwards which start from entity Organization. i have set following in the mapping file to force use inner-join, has anything i missed out in the mapping file? really hope somebody could give me a hint on this. appreciate any helps!
lazy="false" fetch="join"
Example Entites and Relationships:
Sales Record - Employee - Organization
nhibernate generate:
select...
from sales
inner join employee
left outer join organization
Sales.hbm.xml
<many-to-one name="Employee" insert="true" update="true" access="field.pascalcase-underscore" not-null="true" lazy="false" fetch="join"/>
<column name="EmployeeId" not-null="true"/>
</many-to-one>
Employee.hbm.xml
<many-to-one name="Organization" insert="true" update="true" access="field.pascalcase-underscore" not-null="true" lazy="false" fetch="join"/>
<column name="OrgId" not-null="true"/>
</many-to-one>
If NHibernate does an inner join you don't ID from a child and ID from a parent table (but they're the same).
Example:
TableParent (ID, Name)
TableChild (ID, ID_TableParent, ....)
If nHibernate does an inner join, you get:
select c.ID, c.ID_TableParent, p.Name
from TableChild c
inner join TableParent p on p.ID = c.ID_TableParent
If nHibernate does an left outer join, you get:
select c.ID, c.ID_TableParent, p.ID, p.Name
from TableChild c
left outer join TableParent p on p.ID = c.ID_TableParent
And because of the inner workings of NHibernate it can then create 2 entities from the second query. One entity for TableChild and one for TableParent.
In the first query you'd only get TableChild entity and in some cases the p.Name would be ignored (probalby on the second level) and it would requery the database on checking the property that references TableParent.
I found out this when I wanted to load a tree structure with only one hit to the database:
public class SysPermissionTree
{
public virtual int ID { get; set; }
public virtual SysPermissionTree Parent { get; set; }
public virtual string Name_L1 { get; set; }
public virtual string Name_L2 { get; set; }
public virtual Iesi.Collections.Generic.ISet<SysPermissionTree> Children { get; private set; }
public virtual Iesi.Collections.Generic.ISet<SysPermission> Permissions { get; private set; }
public class SysPermissionTree_Map : ClassMap<SysPermissionTree>
{
public SysPermissionTree_Map()
{
Id(x => x.ID).GeneratedBy.Identity();
References(x => x.Parent, "id_SysPermissionTree_Parent");
Map(x => x.Name_L1);
Map(x => x.Name_L2);
HasMany(x => x.Children).KeyColumn("id_SysPermissionTree_Parent").AsSet();
HasMany(x => x.Permissions).KeyColumn("id_SysPermissionTree").AsSet();
}
}
}
And the query I used was this:
SysPermissionTree t = null;
SysPermission p = null;
return db.QueryOver<SysPermissionTree>()
.JoinAlias(x => x.Children, () => t, NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.JoinAlias(() => t.Permissions, () => p, NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.Where(x => x.Parent == null)
.TransformUsing(Transformers.DistinctRootEntity)
.List();
With NHibernate.SqlCommand.JoinType.LeftOuterJoin. Because if I used InnerJoin the structure didn't load with only one query. I had to use LeftOuterJoin, so that NHibernate recognized the entities.
SQL Queries that executed were:
SELECT this_.ID as ID28_2_, this_.Name_L1 as Name2_28_2_, this_.Name_L2 as Name3_28_2_, this_.id_SysPermissionTree_Parent as id4_28_2_, t1_.id_SysPermissionTree_Parent as id4_4_, t1_.ID as ID4_, t1_.ID as ID28_0_, t1_.Name_L1 as Name2_28_0_, t1_.Name_L2 as Name3_28_0_, t1_.id_SysPermissionTree_Parent as id4_28_0_, p2_.id_SysPermissionTree as id4_5_, p2_.ID as ID5_, p2_.ID as ID27_1_, p2_.Name_L1 as Name2_27_1_, p2_.Name_L2 as Name3_27_1_, p2_.id_SysPermissionTree as id4_27_1_ FROM [SysPermissionTree] this_ left outer join [SysPermissionTree] t1_ on this_.ID=t1_.id_SysPermissionTree_Parent left outer join [SysPermission] p2_ on t1_.ID=p2_.id_SysPermissionTree WHERE this_.id_SysPermissionTree_Parent is null
SELECT this_.ID as ID28_2_, this_.Name_L1 as Name2_28_2_, this_.Name_L2 as Name3_28_2_, this_.id_SysPermissionTree_Parent as id4_28_2_, t1_.ID as ID28_0_, t1_.Name_L1 as Name2_28_0_, t1_.Name_L2 as Name3_28_0_, t1_.id_SysPermissionTree_Parent as id4_28_0_, p2_.ID as ID27_1_, p2_.Name_L1 as Name2_27_1_, p2_.Name_L2 as Name3_27_1_, p2_.id_SysPermissionTree as id4_27_1_ FROM [SysPermissionTree] this_ inner join [SysPermissionTree] t1_ on this_.ID=t1_.id_SysPermissionTree_Parent inner join [SysPermission] p2_ on t1_.ID=p2_.id_SysPermissionTree WHERE this_.id_SysPermissionTree_Parent is null
where the first query is left outer join and we get 2 extra fields: t1_.id_SysPermissionTree_Parent as id4_4_, t1_.ID as ID4_
So what I'm trying to tell you is that if you use NHibernate then left outer join is sometimes a must to comply with inner workings of NHibernate.
Related
I have an NHibernate mapping file I want to convert to fluent. I'm stuck with this one particular case:
<many-to-one name="LastChildRevision" update="false" not-found="ignore" access="readonly" fetch="join">
<formula>(SELECT TOP(1) CHILD_REVISION.CHILD_REVISION_ID FROM CHILD_REVISION WHERE CHILD_REVISION.PARENT_ID = PARENT_ID ORDER BY CHILD_REVISION.REVISION_NUMBER DESC)</formula>
</many-to-one>
My class has:
public virtual IList<ChildRevision> ChildRevisions { get; set; }
public virtual ChildRevision LastChildRevision
{
get
{
return this.ChildRevisions.OrderBy(o => o.RevisionNumber).LastOrDefault();
}
}
How can I translate this to Fluent NHibernate? When I try this:
References(x => x.LastChildRevision)
.Formula("(SELECT TOP(1) CHILD_REVISION.CHILD_REVISION_ID FROM CHILD_REVISION WHERE CHILD_REVISION.PARENT_ID = PARENT_ID ORDER BY CHILD_REVISION.REVISION_NUMBER DESC)")
.Access
.ReadOnly()
.Fetch
.Join();
I get this:
Invalid column name 'LastChildRevision_id'.
Thanks!
I know I asked this question a long time ago, but I decided to revisit Fluent NHibernate, and here's what I came up with:
References(x => x.LastChildRevision)
.Column("PARENT_ID")
.Not.Insert()
.Not.Update()
.Access.ReadOnly()
.NotFound.Ignore()
.Cascade.None()
.Formula("(SELECT TOP(1) CHILD_REVISION.CHILD_REVISION_ID FROM CHILD_REVISION WHERE CHILD_REVISION.PARENT_ID = PARENT_ID ORDER BY CHILD_REVISION.REVISION_NUMBER DESC)");
there was a bug you might hit. Try clearing the columns first
References(x => x.LastChildRevision)
.Columns.Clear()
.Formula("(SELEC ...
I have class Place which may or may not have one User.
// I have nothing on User related to Place
public PlaceMap()
{
Id( x=> x.Id, "id").GeneratedBy.Identity();
References(x => x.UserManager, "user_manager_id").Nullable().Cascade.All();
}
When querying over Place, I want always to left join since it is Nullable.
The problem is that the generated sql has inner join.
The query:
var queryList = _dalSession.CreateCriteria<T>();
queryList.CreateAlias("UserManager", "UserManager");
You can explicitly set the join type when calling CreateAlias:
using NHibernate.SqlCommand;
// ...
var queryList = _dalSession.CreateCriteria<T>();
queryList.CreateAlias("UserManager", "UserManager", JoinType.LeftOuterJoin);
If you want to make this behavior the default, you can do so via the mapping.
In mapping by configuration file, specify `fetch="join"
With FluentNH, specify .Fetch.Join()
Using NHibernate mapping-by-code:
classMapper.ManyToOne(
x => x.UserManager,
manyToOneMapper =>
{
manyToOneMapper.Column("user_manager_id");
manyToOneMapper.NotNullable(false);
manyToOneMapper.Lazy(LazyRelation.NoLazy);
manyToOneMapper.Fetch(FetchKind.Join);
}
)
SQL Server syntax is:
select tableAColumn1, tableAColumn2, tableBColumn1
from tableA, tableB
where ISNUMERIC(tableAColumn1) = 1
and CONVERT(INT, tableAColumn1) = tableBColumn1
and tableAColumn2 = 'something'
What would be the best way to achieve this in Fluent NHibernate? How many classes would I need to help get the resulting ClassMap and how would it look like?
Edit:
public class BarausInfoMap : ClassMap<BarausInfo>
{
public BarausInfoMap()
{
Table("BARAUS");
Id(x => x.nr);
Map(x => x.betrag);
Join("BARAUSLANG", m =>
{
m.Fetch.Join();
m.KeyColumn("Ula");
m.Map(x => x.bezeichnung);
m.Map(x => x.sprache);
m.Map(x => x.la);
this.Where("m.la = 'SPE'");
});
}
}
nr column is int and ula column is string, but I need to join those 2. also, the this.where refers to the outer table I guess, it should however refer to the inner table.
Maybe it's better to use separate entities and separate mapping, than you build query like
queryOver<BarausInfo>.JoinQueryOver(x => x.BarauslangObject, barauslangAlias, JoinType.InnerJoin, conjunction)
and the conjunction will contain the ISNUMERIC(tableAColumn1) = 1 and tableAColumn2 = 'something'. And n the BarausInfo Mapping you can spefify something like References(v => v.BarauslangObject).Formula("CONVERT(INT, tableAColumn1) = tableBColumn1"). Just find out how to specify columns in formula properly.
I have to join a table TransactionDeclaration(Id,...) with a parametric function fp_Transaction_ACL(userId) which returns (TransactionDeclarationId,AccessRightId). The join must be an inner join.
I have done the following mapping using Fluent NHibernate:
public TransactionDeclarationMap()
{
this.Id(transactionDeclaration => transactionDeclaration.Id);
this.Join(
"fp_TransactionDeclaration_ACL(:AclFilter.userId)",
join =>
{
join.KeyColumn("TransactionDeclarationId");
join.Map(transactionDeclaration => transactionDeclaration.AccessRight, "AccessRightType").CustomType
<AccessRight>().Generated.Always().ReadOnly();
join.Inverse();
join.Fetch.Join();
});
}
Since the function must not be inserted or updated, I have added the 'join.Inverse()' as explained here :
Nhibernate/hibernate Avoid Insert in joined table or view
But when I add this inverse, the join becomes a left outer join which is not ok for my use case. I need an inner join to filter records of the table 'TransactionDeclaration' not returned by the function.
How can I get an inner join ? Or is there an alternative to the 'Inverse' to avoid inserting in the function ?
I don't know how to do it in Fluent, but you'll have to specify somewhere that the key where you're joining on, is not nullable.
Something like this, perhaps:
public TransactionDeclarationMap()
{
this.Id(transactionDeclaration => transactionDeclaration.Id);
this.Join(
"fp_TransactionDeclaration_ACL(:AclFilter.userId)",
join =>
{
join.KeyColumn("TransactionDeclarationId");
join.Map(transactionDeclaration => transactionDeclaration.AccessRight, "AccessRightType").CustomType
<AccessRight>().Generated.Always().ReadOnly();
join.Inverse();
join.SetAttribute ("optional", false);
join.Fetch.Join();
});
}
click for more info.
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>();
}