NHibernate 3.1.0.4000 QueryOver SQL Optimisation - sql

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

Related

Conditional Queryover depending on the context

I want to do a QueryOver on an object for which I want to do an outer join on its sublist only if the current object has a property set to true.
Let's say you have an object Store which has a list of Product, and Product has a sublist Phone
The object Store has a boolean property HasAllProducts.
If HasAllProducts is true, then Store doesn't need to know the list of products, so no outer join is needed.
IfHasAllProducts is false, then Store has to know its list of Products so an outer join is needed, and another outer join is needed between Product and Phone
So I would like a query like
session.QueryOver<Store>(() => storeAlias)
if (!storeAlias.HasAllProducts)
{
.Left.JoinAlias(s => s.Products, () => productAlias)
}
.Future<Store>();
//Then again to avoid catersian product
if (!storeAlias.HasAllProducts)
{
session.QueryOver<Product>(() => productAlias)
.Left.JoinAlias(p => p.Phones, () => phoneAlias)
.Future<Phone>();
}
I know that for the first join, you can use withClause, but how would you perform (or not perform) the second conditional join? it should not be executed if no Phoneis fetched in the previous query.
The problem with with clause is that it will generate a query in all cases, I would like to avoid running two additional queries if it's not needed.
I would suggest 2 queries.
var stores = session.QueryOver<Store>()
.Where(filter)
.Future();
// only to intialize needed child collections
session.QueryOver<Store>()
.Where(filter)
.Where(s => !s.HasAllProducts)
.JoinQueryOver<Exam>(s => s.Products)
.Fetch(SelectMode.ChildFetch, e => e.Phones) // NHibernate 5
.Fetch(x => x.Phones).Eager // NHibernate 3
.Future();
Another option is to use CollectionBatchSize to not eager load here but use lazy loading with batches on usage

NHibernate filter collection by subcollection items

Health record may have Symptom, which consists of some Words. (ER diagram.)
What I need: by given set of Words return Health records with corresponding Symptoms.
I have this code:
public IEnumerable<HealthRecord> GetByWords(IEnumerable<Word> words)
{
var wordsIds = words.Select(w => w.Id).ToList();
Word word = null;
HealthRecord hr = null;
ISession session = NHibernateHelper.GetSession();
{
return session.QueryOver<HealthRecord>(() => hr)
.WhereRestrictionOn(() => hr.Symptom).IsNotNull()
.Inner.JoinAlias(() => hr.Symptom.Words, () => word)
.WhereRestrictionOn(() => word.Id).IsIn(wordsIds)
.List();
}
}
What we should use here is: INNER SELECT, i.e. subquery. We can do that even with many-to-many maping, but the performance will suffer.
The (easier, my prefered) way would be to not use many-to-many mapping. Because with explicitly mapped pairing object SymptomWord, querying would be much more easier.
Word word = null;
Symptom symptom = null;
// the sub SELECT returning column Symptom.Id
var subq = QueryOver.Of<Symptom>(() => symptom)
// just symptoms refering the searched words
.Inner.JoinAlias(() => symptom.Words, () => word)
.WhereRestrictionOn(() => word.Id).IsIn(wordsIds)
// the result of inner select is
.Select(s => symptom.Id);
And in the next step we can use it for filtering:
var list = session
// just query over HealthRecord here
.QueryOver<HealthRecord>()
.WithSubquery
// the ID of referenced Symptom is in that table
.WhereProperty(hr => hr.Symptom.Id)
// and will be filtered with our subquery
.In(subq)
.List<HelthRecord>();
return list;
That should work, also check some similar issue here:
Query on HasMany reference
NHibernate Lazy Loading Limited by Earlier Criteria
Some hint how to re-map many-to-many (because with a pairing table mapped as an object, we can construct similar and simplified construct, resulting in better SQL Statement)
Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships?

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

For Fluent NHibernate experts: Join with where condition

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.

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