NHibernate queryover with many-to-many - nhibernate

The situation is following:
1. Product belongs to many Categories,
2. Category has many Products.
class Product
{
public int Id { get; set; }
public List<Category> Categories { get; set; }
}
class Category
{
public int Id { get; set; }
public List<Product> Products { get; set; }
}
How to have all products, where each of them belongs to category with id = 2 and 3 and 4. How to do with queryover?
At the moment I use dynamically created hql:
select p from Product p where
exists (select c.id from p.Categories c where c.Id = 2)
and exists (select c.id from p.Categories c where c.Id = 3)
and exists (select c.id from p.Categories c where c.Id = 4)
And the mapping is:
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
HasManyToMany(x => x.Categories)
.Table("product_category")
.ParentKeyColumn("product_id")
.ChildKeyColumn("category_id");
}
}
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
HasManyToMany(x => x.Products)
.Table("product_category")
.ParentKeyColumn("category_id")
.ChildKeyColumn("product_id");
}
}

Try this, maybe it's help:
Category categoryAlias = null;
session.QueryOver<Product>()
.JoinAlias(product => product.Categories, () => categoryAlias)
.WhereRestrictionOn(() => categoryAlias.Id).IsIn(new [] { 2, 3, 4 })
.List();

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 JoinQueryOver query in Orchard

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/)!

Join table on custom columns

I want to join table based NOT on their Database-level relationship (PK, FK), but some custom criteria:
SELECT * FROM Users u inner join Docs d on u.Doc = d.ISBN
and map it to
public class User
{
public Doc Document { get; set; }
}
public class Doc
{
public Int64 Id {get; protected set}
public Int64 Isbn {get; set}
}
Is it possible with only mappings or do I need to call some method on Criteria API (which I use)?
I have tried the following:
public class UserMap : ClassMapping<User>
{
public UserMap()
{
Table("Users");
Id(x => x.Id, p => p.Column("Id"););
ManyToOne(x => x.Document, m =>
{
m.Column("Doc");
m.ForeignKey("ISBN");
m.Fetch(FetchKind.Join);
m.Access(Accessor.Property);
m.Lazy(LazyRelation.NoLazy);
m.NotNullable(true);
});
}
}
public class DocMap : ClassMapping<Doc>
{
public DocMap()
{
Table("Docs");
Id(x => x.Id, p =>
{
p.Column("Id");
p.Generator(Generators.GuidComb);
});
Property(x => x.Isbn, p =>
{
p.Column("ISBN");
p.NotNullable(true);
});
}
}
//2.Criteria API
var criteria = Session.CreateCriteria("User")
// ...
criteria.CreateCriteria("Document", "Docs", JoinType.InnerJoin, docCriterion);
but the result is:
SELECT * FROM Users u
inner join Docs d on u.Doc=d.Id /* docCriterion */
left outer join Docs d2 on u.Doc=d.Id
try this
criteria.CreateCriteria("Users")
.CreateCriteria("Document").Add(Restrictions.Eq("Isbn ", ?);
in your query u.Doc = d.ISBN is not correct, cause Doc is Doc entity and ISBN is number

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