For Fluent NHibernate experts: Join with where condition - nhibernate

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.

Related

Nhibernate mapping one to zero or one - with left join

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);
}
)

NHibernate 3.1.0.4000 QueryOver SQL Optimisation

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>();
}

NHibernate using wrong table alias

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).

HasMany relation inside a Join Mapping

So, I'm having a problem mapping in fluent nhibernate. I want to use a join mapping to flatten an intermediate table: Here's my structure:
[Vehicle]
VehicleId
...
[DTVehicleValueRange]
VehicleId
DTVehicleValueRangeId
AverageValue
...
[DTValueRange]
DTVehicleValueRangeId
RangeMin
RangeMax
RangeValue
Note that DTValueRange does not have a VehicleID. I want to flatten DTVehicleValueRange into my Vehicle class. Tgis works fine for AverageValue, since it's just a plain value, but I can't seem to get a ValueRange collection to map correctly.
public VehicleMap()
{
Id(x => x.Id, "VehicleId");
Join("DTVehicleValueRange", x =>
{
x.Optional();
x.KeyColumn("VehicleId");
x.Map(y => y.AverageValue).ReadOnly();
x.HasMany(y => y.ValueRanges).KeyColumn("DTVehicleValueRangeId"); // This Guy
});
}
The HasMany mapping doesn't seem to do anything if it's inside the Join. If it's outside the Join and I specify the table, it maps, but nhibernate tries to use the VehicleID, not the DTVehicleValueRangeId.
What am I doing wrong?
Can you explain the average value column in the DTVehicleValueRange table? Isn't this a calculated value (i.e. no need to persist it)?
It looks like you have a many-to-many relationship between Vehicle and DTValueRange, which of course would not be mapped with a join, rather with a HasManyToMany call.
Ran into a similar issue today using a Map to create a view. The SQL generated showed it trying to do the HasMany<> inside the join based on the Id of the ParentThing and not WorkThing (same problem you were having)
After much mapping of head-to-desk it turns out adding the propertyref onto the hasmany solved it.
public class ThingMap : ClassMap<WorkThingView> {
public ThingMap() {
ReadOnly();
Table("ParentThing");
Id(x => x.ParentThingId);
Map(x => x.ParentName);
Join("WorkThing", join => {
join.KeyColumn("ParentThingId");
join.Map(m => m.FooCode);
join.Map(m => m.BarCode);
join.Map(x => x.WorkThingId);
join.HasMany(x => x.WorkThingCodes)
.Table("WorkThingCode").KeyColumn("WorkThingId").PropertyRef("WorkThingId")
.Element("WorkThingCode");
});
}
}

NHibernate: why doesn't my profiler query correspond to my fluent mapping?

I have this fluent mapping:
sealed class WorkPostClassMap : ClassMap<WorkPost>
{
public WorkPostClassMap()
{
Not.LazyLoad();
Id(post => post.Id).GeneratedBy.Identity().UnsavedValue(0);
Map(post => post.WorkDone);
References(post => post.Item).Column("workItemId").Not.Nullable();
References(Reveal.Property<WorkPost, WorkPostSheet>("Owner"), "sheetId").Not.Nullable();
}
parent class:
sealed class WorkPostSheetClassMap : ClassMap<WorkPostSheet>
{
public WorkPostSheetClassMap()
{
Id(sheet => sheet.Id).GeneratedBy.Identity().UnsavedValue(0);
Component(sheet => sheet.Period, period =>
{
period.Map(p => p.From, "PeriodFrom");
period.Map(p => p.To, "PeriodTo");
});
References(sheet => sheet.Owner, "userId").Not.Nullable();
HasMany(sheet => sheet.WorkPosts).KeyColumn("sheetId").AsList();
}
WorkItem class:
sealed class WorkItemClassMap : ClassMap<WorkItem>
{
public WorkItemClassMap()
{
Not.LazyLoad();
Id(wi => wi.Id).GeneratedBy.Assigned();
Map(wi => wi.Description).Length(500);
Version(wi => wi.LastChanged).UnsavedValue(new DateTime().ToString());
}
}
And when a collection of WorkPosts are lazy loaded from the owning work post sheet I get the following select statement:
SELECT workposts0_.sheetId as sheetId2_,
workposts0_.Id as Id2_,
workposts0_.idx as idx2_,
workposts0_.Id as Id2_1_,
workposts0_.WorkDone as WorkDone2_1_,
workposts0_.workItemId as workItemId2_1_,
workposts0_.sheetId as sheetId2_1_,
workitem1_.Id as Id1_0_,
workitem1_.LastChanged as LastChan2_1_0_,
workitem1_.Description as Descript3_1_0_
FROM "WorkPost" workposts0_
inner join "WorkItem" workitem1_ on workposts0_.workItemId=workitem1_.Id
WHERE workposts0_.sheetId=#p0;#p0 = 1
No, there are a couple of things here which doesn't make sence to me:
The workpost "Id" property occurs twice in the select statement
There is a select refering to a column named "idx" but that column is not a part of any fluent mapping.
Anyone who can help shed some light on this?
The idx column is in the select list because you have mapped Sheet.WorkPosts as an ordered list. The idx column is required to set the item order in the list. I'm not sure why the id property is in the select statement twice.
By the way, an unsaved value of 0 is the default for identity fields, so you can remove .UnsavedValue(0) if you want.