NHibernate JoinQueryOver query in Orchard - nhibernate

I have an Orchard website. There are two connected entities: agencies (AgencyPartRecord ) and facilities (FacilityPartRecord), they are connected n-to-n with AgencyFacilitiesPartRecord. Here are the corresponding records:
public class AgencyPartRecord : ContentPartRecord
{
...
public virtual IList<AgencyFacilitiesPartRecord> AgencyFacilitiesPartRecords { get; set; }
...
}
public class AgencyFacilitiesPartRecord
{
public virtual int Id { get; set; }
public virtual AgencyPartRecord AgencyPartRecord { get; set; }
public virtual FacilityPartRecord FacilityPartRecord { get; set; }
}
public class FacilityPartRecord : ContentPartRecord
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Now I need to filter out agencies by the set of facilities, so that only agencies having all the facilities in list should by selected.
In the end I want to get SQL like this:
SELECT *
FROM AgencyPartRecord
WHERE Id IN
(
SELECT a.AgencyPartRecord_Id
FROM AgencyFacilitiesPartRecord a
WHERE a.FacilityPartRecord_Id IN (... filter values here ...)
GROUP BY a.AgencyPartRecord
HAVING COUNT(a.Id) = <number of filter values>
)
I have written the following query (filter.Facilities is of type List<int>):
IQueryOver<AgencyPartRecord, AgencyPartRecord> agencies = ... // apply other filters
AgencyFacilitiesPartRecord facility = null;
var fsub = QueryOver.Of<AgencyFacilitiesPartRecord>(() => facility)
.WhereRestrictionOn(r => r.FacilityPartRecord.Id).IsIn(filter.Facilities)
.SelectList(list =>
list
.SelectGroup(a => a.AgencyPartRecord.Id)
.SelectCount(a => a.FacilityPartRecord.Id))
.Where(Restrictions.Eq(
Projections.Count(
Projections.Property(() => facility.FacilityPartRecord.Id)), filter.Facilities.Count))
.SelectList(list => list.Select(a => a.AgencyPartRecord.Id));
agencies = agencies
.WithSubquery.WhereProperty(a => a.Id).In(fsub);
The problem is this query does not produce GROUP BY clause in SQL:
SELECT ...
FROM AgencyPartRecord this_
WHERE ...
and this_.Id in
(
SELECT this_0_.AgencyPartRecord_id as y0_
FROM AgencyFacilitiesPartRecord this_0_
WHERE this_0_.FacilityPartRecord_id in (#p2, #p3)
HAVING count(this_0_.FacilityPartRecord_id) = #p4
)
What am I doing wrong?
Thanks!

I finally got it! :)
The right code is:
AgencyFacilitiesPartRecord facility = null;
var fsub = QueryOver.Of<AgencyFacilitiesPartRecord>(() => facility)
.WhereRestrictionOn(r => r.FacilityPartRecord.Id).IsIn(filter.Facilities)
.SelectList(list => list
.SelectGroup(r => r.AgencyPartRecord.Id)
)
.Where(Restrictions.Eq(
Projections.Count(Projections.Property(() => facility.FacilityPartRecord.Id)), filter.Facilities.Count));
agencies = agencies.WithSubquery.WhereProperty(a => a.Id).In(fsub);
Andrew, thanks again for your QueryOver series (http://blog.andrewawhitaker.com/queryover-series/)!

Related

nhibernate child collection limitation

I have these classes:
public class Document
{
public Document()
{
Descriptions = new List<Descriptions>();
}
public virtual int Id { get; set; }
public virtual IList<DocumentDescription> Descriptions { get; set; }
}
public class DocumentDescription
{
public virtual int DocumentId { get; set; }
public virtual int LanguageId { get; set; }
}
and mappings:
public DocumentMap()
{
Id(x => x.Id);
HasMany(x => x.Descriptions).KeyColumn("DocumentId");
}
public DocumentDescriptionMap()
{
CompositeId()
.KeyProperty(x => x.DocumentId)
.KeyProperty(x => x.LanguageId);
}
my query is:
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds)).List();
I need a query over solution to restrict DocumentDescriptions by few languages, which I will get run-time. I don't want to get all DocumentDescriptions for one Document (only few). Is it possible to set filter/limitation for a child collection?
So, I find out how to add additional join clause to my query:
DocumentDescription dd = null;
ICriterion criterion = Restrictions.On<DocumentDescription>(x => x.LanguageId).IsIn(languageIds.ToArray());
var query = Session.QueryOver<Document>().Where(x => x.Id.IsIn(documentIds));
query.Left.JoinQueryOver(x => x.Descriptions, () => dd, criterion);
SQL:
SELECT * FROM tDocument
LEFT OUTER JOIN tDocumentDescription ON tDocumentDescription.DocumentId = tDocument.Id AND tDocumentDescription.LanguageId IN (#languageIds)
WHERE tDocument.Id IN (#documentIds)

nHibernate QueryOver Inheritance

I have something like this :
public class User
{
/* some properties */
public virtual int Id { get; set; }
public abstract string LastName { get; }
}
public class Student
{
public virtual int Id { get; set; }
public virtual string LastName{ get; set; }
}
public class StudentUser : User
{
public virtual Student Student { set; get; }
public override string LastName
{
get { return Student.LastName; }
}
}
public class foo
{
public virtual int Id { get; set; }
public virtual IList<User> Users { get; set; }
}
1. I would like to do the following using QueryOver :
var query = session.QueryOver<foo>();
User User = null;
//case 1: This is not working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).Student.LastName == "smith" );
//case 2: Also This is not working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).LastName == "smith" );
//case 3: This is working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).Id == 123 );
When I use query.JoinAlias(x=> x.Users , () => User ) more than one time, I get this exception An item with the same key has already been added.
Also I get this exception : could not resolve property: Student.LastName of: Entities.User in case 1 and case 2
But it is working with the Id property (case 3)
It might be look like :
SELECT *
FROM Foo INNER JOIN
Users ON Foo.Id = Users.FooId_FK INNER JOIN
Students ON Users.StudentId_FK = Students.Id
where Students.LastName = 'smith'
How can I get the LastName property value?
I think this should work:
session.QueryOver<foo>()
.JoinQueryOver(f => f.Users)
.JoinQueryOver(u => ((StudentUser)u).Student)
.Where(st => st.LastName == "smith")
Or keeping with JoinAlias:
User userAlias = null;
Student studentAlias = null;
session.QueryOver<foo>()
.JoinAlias(f => f.Users, () => userAlias)
.JoinAlias(() => ((StudentUser)userAlias).Student, () => studentAlias)
.Where(() => studentAlias.LastName == "smith")

Nhibernate self join read join field value

I intend to read the referencing field (ParentMenu) value for my self join mapping. The class and mappings are as follows;
public class MenuSetup
{
public virtual int MenuId { get; set; }
public virtual string DisplayText { get; set; }
public virtual int MenuOrder { get; set; }
public virtual bool MenuStatus { get; set; }
public virtual bool HasKids { get; set; }
public virtual MenuSetup Parent { get; set; }
public virtual ICollection<MenuSetup> SubMenu { get; set; }
}
Mappings below;
public MenuSetupMap()
{
Id(x => x.MenuId).GeneratedBy.Identity();
Map(x => x.DisplayText);
Map(x => x.MenuStatus);
Map(x => x.MenuOrder);
Map(x => x.HasKids);
HasMany(x => x.SubMenu).KeyColumn("ParentMenu");
References(x => x.Parent).Column("ParentMenu");
.Cascade.AllDeleteOrphan()
.Fetch.Join().Inverse().KeyColumn("MenuId");
}
There is ParentMenu field that i want to read like this into my view model HomeMenuViewModel.
var session = MvcApplication.SessionFactory.GetCurrentSession();
string qry = #"select p.MenuId,p.DisplayText,p.MenuStatus,p.MenuOrder,
m from MenuSetup as p left join p.Parent m";
var vm=session.CreateQuery(qry).List<object[]>()
.Select(x=>new HomeMenuViewModel()
{
MenuId=(int)x[0],
DisplayText=(string)x[1],
MenuStatus=(Boolean)x[2],
MenuOrder=(int)x[3],
ParentMenu = x[10] == null ? 0 : (int)x[10]
}).ToList();
throws an error "System.IndexOutOfRangeException was unhandled by user code". I am stuck really need help.
The query generated from NHProf is shown below;
select menusetup0_.MenuId as col_0_0_,
menusetup0_.DisplayText as col_1_0_,
menusetup0_.MenuStatus as col_2_0_,
menusetup0_.MenuOrder as col_3_0_,
menusetup1_.MenuId as col_4_0_,
menusetup1_.MenuId as MenuId10_,
menusetup1_.DisplayText as DisplayT2_10_,
menusetup1_.MenuStatus as MenuStatus10_,
menusetup1_.MenuOrder as MenuOrder10_,
menusetup1_.HasKids as HasKids10_,
menusetup1_.ParentMenu as ParentMenu10_
from [MenuSetup] menusetup0_
left outer join [MenuSetup] menusetup1_
on menusetup0_.ParentMenu = menusetup1_.MenuId
Waiting for help in earnest.
Thank you
UPDATE SOLUION
I found the solution after much tinkering
I needed to declare the field in the MenuSetup class as below
public class MenuSetup
{
.
.
public virtual int ParentMenu { get; set; }
.
}
In the Mapping class, i declared the column as well.
My retrieval code changed to
var session = MvcApplication.SessionFactory.GetCurrentSession();
string qry = #"select p.MenuId,p.DisplayText,p.MenuStatus,p.MenuOrder,
p.ParentMenu from MenuSetup as p";
var vm=session.CreateQuery(qry).List<object[]>()
.Select(x=>new HomeMenuViewModel()
{
MenuId=(int)x[0],
DisplayText=(string)x[1],
MenuStatus=(Boolean)x[2],
MenuOrder=(int)x[3],
ParentMenu = x[4] == null ? 0 : (int)x[4]
}).ToList();
The amount of object[] items is related to the HQL query, not to the generated SQL query. I.e. the above query will return object[] having 5 elements:
// the HQL Snippet
select
p.MenuId, // object[0]
p.DisplayText, // object[1]
p.MenuStatus, // object[2]
p.MenuOrder, // object[3]
m // object[4]
from MenuSetup ....
So, there is no object[10] ... and that's why we do have System.IndexOutOfRangeException
The 5th element is complete parent. The solution should be like this:
ParentMenu = x[4] == null ? null : (MenuSetup)x[4]
NOTE: it could be better if we would use only one property from parent, e.g. change HQL to ...m.DisplayText...

NHibernate: Projecting child entities into parent properties throws an exception

I have the following parent entity Department which contains a collection of child entities Sections
public class Department
{
private Iesi.Collections.Generic.ISet<Section> _sections;
public Department()
{
_sections = new HashedSet<Section>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<Section> Sections
{
get { return _sections; }
}
public virtual int Version { get; set; }
}
public partial class Section
{
public Section()
{
_employees = new HashedSet<Employee>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual Department Department { get; protected set; }
public virtual int Version { get; set; }
}
I would like to transform (flatten) it to the following DTO
public class SectionViewModel
{
public string DepartmentName { get; set; }
public string SectionName { get; set; }
}
Using the following code.
SectionModel sectionModel = null;
Section sections = null;
var result = _session.QueryOver<Department>().Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property<Department>(d => sections.Department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property<Department>(s => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I am however getting the following exception: could not resolve property: Department.Name of: Domain.Section
I have even tried the following LINQ expression
var result = (from d in _session.Query<Department>()
join s in _session.Query<Section>()
on d.Id equals s.Department.Id into ds
from sm in ds.DefaultIfEmpty()
select new SectionModel
{
DepartmentName = d.Name,
SectionName = sm.Name ?? null
}).ToList();
Mappings
public class DepartmentMap : ClassMapping<Department>
{
public DepartmentMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
Set(x => x.Sections,
m =>
{
m.Access(Accessor.Field);
m.Inverse(true);
m.BatchSize(20);
m.Key(k => { k.Column("DeptId"); k.NotNullable(true); });
m.Table("Section");
m.Cascade( Cascade.All | Cascade.DeleteOrphans);
},
ce => ce.OneToMany());
}
}
public class SectionMap : ClassMapping<Section>
{
public SectionMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
ManyToOne(x => x.Department,
m =>
{
m.Column("DeptId");
m.NotNullable(true);
});
}
}
But this throws a method or operation is not implemented.
Seeking guidance on what I am doing wrong or missing.
NHibernate doesn't know how to access a child property's child through the parent entity. A useful thing to remember about QueryOver is that it gets translated directly into SQL. You couldn't write the following SQL:
select [Section].[Department].[Name]
right? Therefore you can't do the same thing in QueryOver. I would create an alias for the Department entity you start on and use that in your projection list:
Department department;
Section sections;
var result = _session.QueryOver<Department>(() => department)
.Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property(() => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I noticed in your comment you'd like an order by clause. Let me know if you need help with that and I can probably come up with it.
Hope that helps!
This may be now fixed in 3.3.3. Look for
New Feature
[NH-2986] - Add ability to include collections into projections
Not sure but if this is your problem specifically but if you are not using 3.3.3 then upgrade and check it out.
Aslo check out the JIRA
Have you tried a linq query like
from d in Departments
from s in d.Sections
select new SectionModel
{
DepartmentName = d.Name,
SectionName = s == null ? String.Empty : s.Name
}

Nhibernate return a specific type of union subclass in a QueryOver with join

This is my current nhibenate query
MappedHSPItemDto itemDtoAlias = null;
TItem itemAlias = default(TItem);
return
Session.QueryOver<TMapItem>()
.JoinAlias(x => x.Item, () => itemAlias, JoinType.InnerJoin)
.Where(x => x.HealthServiceProvider == hsp)
.SelectList(list => list
.Select(x => x.Id).WithAlias(() => itemDtoAlias.Id)
.Select(x => x.Version).WithAlias(() => itemDtoAlias.Version)
.Select(x => x.HSPItemCode).WithAlias(() => itemDtoAlias.HSPItemCode)
.Select(x => x.HSPItemName).WithAlias(() => itemDtoAlias.HSPItemName)
.Select(x => itemAlias.Code).WithAlias(() => itemDtoAlias.CirrusItemCode)
.Select(x => itemAlias.Name).WithAlias(() => itemDtoAlias.CirrusItemName)
)
.TransformUsing(Transformers.AliasToBean<MappedHSPItemDto>()).List<MappedHSPItemDto>();
which returns this sql query
SELECT
this_.Id as y0_,
this_.Version as y1_,
this_.HSPItemCode as y2_,
this_.HSPItemName as y3_,
itemalias1_.Code as y4_,
itemalias1_.Name as y5_
FROM
HSPMedicineMapping this_
inner join
(
select
Id,
Version,
Code,
Name,
GenericName,
1 as clazz_
from
Medicine
union
all select
Id,
Version,
Code,
Name,
null as GenericName,
2 as clazz_
from
AssetEquipment
union
all select
Id,
Version,
Code,
Name,
null as GenericName,
3 as clazz_
from
[Procedure]
union
all select
Id,
Version,
Code,
Name,
null as GenericName,
4 as clazz_
from
Supply
union
all select
Id,
Version,
Code,
Name,
null as GenericName,
5 as clazz_
from
Examination
union
all select
Id,
Version,
Code,
Name,
null as GenericName,
6 as clazz_
from
OtherItem
) itemalias1_
on this_.ItemId=itemalias1_.Id
WHERE
this_.HealthServiceProviderId = #p0;
#p0 = 12 [Type: Int64 (0)]
basically what is happening here is im joining to a unionsubclass and on the query its including all subclass of the Item type.
Is there a way to just include the specific type of subclass in a query?
im using mapping by code, below is the mapping for one of the subclass
public class MedicineMap : UnionSubclassMapping<Medicine>
{
public MedicineMap()
{
Property(p => p.Code, Rules.CodeRule);
Property(p => p.Name, Rules.StrLength255AndNotNull);
Property(p => p.GenericName, Rules.StrLength400AndNullable);
Bag(x => x.HMOMedicineMappings, bag =>
{
bag.Inverse(true);
bag.Key(k => k.Column(col => col.Name("ItemId")));
}, a => a.OneToMany());
Bag(x => x.HSPMedicineMappings, bag =>
{
bag.Inverse(true);
bag.Key(k => k.Column(col => col.Name("ItemId")));
}, a => a.OneToMany());
}
}
Here is my entities
public abstract class Item : EntityBase
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
}
public class Medicine : Item
{
public Medicine()
{
HSPMedicineMappings = new List<HSPMedicineMapping>();
HMOMedicineMappings = new List<HMOMedicineMapping>();
}
public virtual string GenericName { get; set; }
public virtual IList<HSPMedicineMapping> HSPMedicineMappings { get; set; }
public virtual IList<HMOMedicineMapping> HMOMedicineMappings { get; set; }
}
public class AssetEquipment : Item
{
public AssetEquipment()
{
HSPAssetEquipmentMappings = new List<HSPAssetEquipmentMapping>();
HMOAssetEquipmentMappings = new List<HMOAssetEquipmentMapping>();
}
public virtual IList<HSPAssetEquipmentMapping> HSPAssetEquipmentMappings { get; set; }
public virtual IList<HMOAssetEquipmentMapping> HMOAssetEquipmentMappings { get; set; }
}
public abstract class HSPItemMapping : EntityBase
{
public virtual HealthServiceProvider HealthServiceProvider { get; set; }
public virtual string HSPItemCode { get; set; }
public virtual string HSPItemName { get; set; }
public virtual Item Item { get; set; }
}
public class HSPMedicineMapping : HSPItemMapping
{
}
if TMapItem is the specific type NH will only query the table of TMapItem. However as you said in comment when joining the Type is unknown hence all tables are unioned. To avoid this you have to introduce a column next to the foreignkey holding the type. Im not sure but NH should optimise the query then.
// mapping using FluentNhibernate
ReferencesAny(x => x.Property)
.KeyType()
.MetaValue<Subclass1>("foo")
.MetaValue<Subclass2>("bar");