Nhibernate self join read join field value - nhibernate

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

Related

Fluent Nhibernate generating incorrect query resulting in Cartesian product instead of single row

After trying numerous things, I am still unable to figure out the right way to query the following relationship using Fluent NHibernate.
This would otherwise have been an easier task if I had to write SQL queries. Hope to get some better advice to avoid N+1 issue and unoptimized auto-generated SQL queries.
I have the following relationship which goes something as below :
Tags can have media content (Images of various predefined sizes, videos, documents etc.) associated with it.
- 1 Tags can have multiple Media Items mapped to it (Lets's say images with dimension 32x32, 64x64, 600x100, 0 or more Videos)
- Every media item is mapped to a media description which helps in identifying the size and type of the media
- The same media item can be used by a different tag. Example, having a generic image for all tags which do not have any icons.
Entities:
Media
public class Media:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string FilePath { get; set; }
public virtual MediaType MediaType { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
protected set { _tagMedia= value; }
}
}
Tag
public class Tag:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
set { _tagMedia= value; }
}
}
TagMedia
public class TagMedia :IEntity
{
public virtual int Id { get; set; }
public virtual Media Media { get; set; }
public virtual Tag Tag { get; set; }
public virtual DateTime AddedOn { get; set; }
}
MediaType
public class MediaType:IEntity
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
}
Mappings
MediaMapping
public class MediaMapping : IAutoMappingOverride<Media>
{
public void Override(AutoMapping<Media> mapping)
{
mapping.Map(c => c.FileName).CustomSqlType("varchar(60)").Not.Nullable();
}
}
TagMapping
public class TagMapping : IAutoMappingOverride<Tag>
{
public void Override(AutoMapping<Tag> mapping)
{
mapping.HasMany<TagMedia>(c => c.TagMedia)
.KeyColumn("TagId")
.Cascade.SaveUpdate()
.BatchSize(25);
mapping.BatchSize(25);
mapping.DynamicUpdate();
mapping.DynamicInsert();
}
}
TagMediaMapping
public class TagMediaMapping : IAutoMappingOverride<TagMedia>
{
public void Override(AutoMapping<TagMedia> mapping)
{
mapping.Map(c=>c.AddedOn);
}
}
Query:
The following query gets the cartesian product of all media mapped to the tag and does not eliminate records other than "Icon-16x16". I expect the ORM to return no more than one row.
Any help would be highly appreciated.
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.SingleOrDefault();
Generated SQL:
exec sp_executesql N'select *
from [Tag] Tag0_ left outer join [TagMedia] Tagme1_ on Tag0_.TagId=Tagme1_.TagId
left outer join [Media] media2_ on Tagme1_.MediaId=media2_.MediaId
left outer join [MediaType] mediatype3_ on media2_.MediaTypeId=mediatype3_.MediaTypeId
where Tag0_.TagId=#p0
and (exists (select Tagme4_.TagMediaId
from [TagMedia] Tagme4_ inner join [Media] media5_ on Tagme4_.MediaId=media5_.MediaId inner join [MediaType] mediatype6_ on media5_.MediaTypeId=mediatype6_.MediaTypeId where Tag0_.TagId=Tagme4_.TagId and mediatype6_.MediaTypeDescription=#p1))',N'#p0 int,#p1 nvarchar(4000)',#p0=102,#p1=N'Icon-16x16'
go
the SingleOrDefault function is a LINQ function and not implemented by NHibernate. Use .Take(1)
I think the solution is:
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.Take(1)
.SingleOrDefault();

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)

Convert CASE WHEN FROM SQL to NHIBERNATE

I have a SQL statement below that needs to convert it to NHibrnate QueryOver. I have searched the web but can't find a concrete solution. Anyone can help me on this?
SELECT
TABEL1.Id,
CASE WHEN EXISTS (SELECT Id FROM TABLE2 WHERE TABLE2.ID = TABLE1.ID)
THEN 'TRUE'
ELSE 'FALSE'
END AS NewFiled
FROM TABLE1
--Here is the real POCO
public class UserRole
{
[Required]
public virtual User User { get; set; }
[Required]
public virtual Role Role { get; set; }
}
public class UserTenant
{
[Required]
public virtual Tenant Tenant { get; set; }
[Required]
public virtual User User { get; set; }
}
public class Role
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string RoleLabel { get; set; }
[StringLength(4000), Required]
public virtual string RoleDescription { get; set; }
}
public class User
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string Firstname { get; set; }
[StringLength(255), Required]
public virtual string Lastname { get; set; }
}
public class Tenant
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string Name { get; set; }
[StringLength(4000), Required]
public virtual string Description { get; set; }
}
public class AssignRoleUsersModel
{
public virtual int UserId { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual bool IsAssigned { get; set; }
}
--This is the method to get the users assigned or not for that particular role.
public RoleUsers GetRoleUsers(int Id)
{
UserRole userRolesAlias = null;
UserTenant userTenantsAlias = null;
-- This query will get the role depending the Id that have passed. Take note that I don't want to use this query that is why I am converting it to queryOver.
var role = (from r in RoleRepository.Queryable()
where r.Id == Id
select r).FirstOrDefault();
--This query will get all the users having the role result above.Take note that I don't want to use this query that is why I am converting it to queryOver.
var assignedUsers = UserRoleRepository.Queryable().Where(x => x.Role.Id == role.Id).Select(a => a.User.Id).ToArray();
--This is the condition to know if the user was assigned to the role
var projection = Projections.Conditional(Restrictions.Where(() => userRolesAlias.User.Id.IsIn(assignedUsers))
, Projections.Constant(true)
, Projections.Constant(false)
);
var users =
new List<AssignRoleUsersModel>(UnitOfWorkLocalData.CurrentUnitOfWork.Session.QueryOver(() => userTenantsAlias)
.Select(x => x.User.Id)
.Select(x => x.User.LastName)
.Select(x => x.User.FirstName)
.Select(x => x.User.UserName)
.Select(projection)
.TransformUsing(Transformers.AliasToBean<AssignRoleUsersModel>())
.List<AssignRoleUsersModel>());
}
EXTENDED based on the extended question, the way how to do CASE WHEN:
// the ID of searched role, coming as parameter id
int searchedRolId = ...
UserRole userRolesAlias = null;
// this is the SUBQUERY we need, the inner select
var subquery = QueryOver
.Of<UserRole>(() => userRolesAlias)
.Where(() => userRolesAlias.Role.Id == searchedRolId)
.Select(x => userRolesAlias.User.Id);
// here we use NHibernate built in Subqueries tools
var projection = Projections.Conditional(
Subqueries.Exists(subquery.DetachedCriteria) // this is the SUB-SELECT
, Projections.Constant(true)
, Projections.Constant(false)
);
ORIGINAL - This could look like this:
var projection = Projections.Conditional(
NHibernate.Criterion.Expression
.Sql("EXISTS (SELECT Id FROM TABLE2 WHERE TABLE2.ID = {alieas}.ID)")
, Projections.Constant(true)
, Projections.Constant(false)
);
var query = session.QueryOver<Table1>();
ResultDto result = null;
var list = query.SelectList(l => l
.Select(x => x.ID).WithAlias(() => result.ID)
.Select(projection).WithAlias(() => result.DoesExist)
)
.TransformUsing(Transformers.AliasToBean<ResultDto>())
.List<ResultDto>();
The list here contains the set of ResultDtos, with ID and decision if the subtabel TABLE2 record exists. The {alias} will be replaced by NHibernate with the alias of the outert table...
Where this is our DTO for projections:
public class ResultDto
{
public virtual int ID { get; set; }
public virtual bool DoesExist { get; set; }
}

Fluent NHibernate Mapping is set to AllDeleteOrphan but is still trying to null the foreign key in the DB

I'm getting an error with NHibernate when I try to perfrom a ISession.Delete on any table with a One to Many relationship.
NHibernate is trying to set the foreign key to the parent table in the child table to null, rather than just deleting the child table row.
Here is my domain:
public class Parent
{
public Parent()
{
_children = new List<Child>();
}
public int Id { get; set; }
public string SimpleString { get; set; }
public DateTime? SimpleDateTime { get; set; }
private IList<Child> _children;
public IEnumerable<Child> Children
{
get { return _children; }
}
public void AddChild(Child child)
{
child.Parent = this;
_children.Add(child);
}
}
public class Child
{
public int Id { get; set; }
public string SimpleString { get; set; }
public DateTime? SimpleDateTime { get; set; }
[JsonIgnore]
public Parent Parent { get; set; }
}
I have set-up the Fluent NHibernate mappings as follows:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Not.LazyLoad();
Id(x => x.Id);
Map(x => x.SimpleString);
Map(x => x.SimpleDateTime);
HasMany(x => x.Children)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.AllDeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Not.LazyLoad();
Id(x => x.Id);
Map(x => x.SimpleString);
Map(x => x.SimpleDateTime);
References(x => x.Parent).Not.Nullable().Column("ParentId").Cascade.All().Fetch.Join();
}
}
I've told NHibernate to Cascade.AllDeleteOrphan() but it's still trying to set the ParentId foriegn key to null here is the test I setup:
public void Delete_GivenTableWithChildren_WillBeDeletedFromDB()
{
int id;
using (var createSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var parent = new Parent();
parent.AddChild(new Child { SimpleString = "new child from UI" });
using (var trx = createSession.BeginTransaction())
{
createSession.Save(parent);
trx.Commit();
id = parent.Id;
}
}
using (var firstGetSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var result = firstGetSession.Get<Parent>(id);
Assert.IsNotNull(result);
}
using (var deleteSession = MsSqlSessionProvider.SessionFactory.OpenSession())
{
using (var trx = deleteSession.BeginTransaction())
{
deleteSession.Delete("from " + typeof(Parent).Name + " o where o.Id = :Id", id, NHibernateUtil.Int32);
trx.Commit();
}
}
using (var session = MsSqlSessionProvider.SessionFactory.OpenSession())
{
var result = session.Get<Parent>(id);
Assert.IsNull(result);
}
}
Which is failing on the deleteSession.Delete line after attempting the following SQL:
exec sp_executesql N'UPDATE [Child] SET ParentId = null WHERE ParentId = #p0',N'#p0 int',#p0=5
with:
NHibernate.Exceptions.GenericADOException : could not delete collection: [SaveUpdateOrCopyTesting.Parent.Children#5][SQL: UPDATE [Child] SET ParentId = null WHERE ParentId = #p0]
----> System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ParentId', table 'SaveUpdateCopyTestingDB.dbo.Child'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
Does anyone know what I've done wrong in my mappings, or know of a way to stop NHibernate from attempting to null the foreign key id?
Thanks
Dave
Try setting .Inverse() on the ParentMap's HasMany, so it looks like:
HasMany(x => x.Children)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.AllDeleteOrphan().Inverse()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
I'm not sure if cascade works if you delete with HQL.
Try this:
var parent = deleteSession.Load<Parent>(id)
deleteSession.Delete(parent);
It's a pity that you don't have lazy loading. Load would not need the entity to be read from the database, it would just create a proxy in memory.

get property count with entity using nhibernate

can I am hoping someone can point me to the right direction on how to get count of a property and the entity using a single trip to sql.
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public virtual int ArticlesCount { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
Cache.ReadWrite();
}
}
My goal is to get the all Categories and the count of the associated articles if there is any.
I have tried this
ICriteria crit = session.CreateCriteria(typeof(Category));
crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Description"), "Description")
.Add(Projections.Count("Articles"), "ArticlesCount"));
crit.SetResultTransformer(Transformers.AliasToBean (typeof(Category)));
var aa=crit.List();
unfortunately the generated sql shows the count of the Category table not the Articles list.
Thanks
You could use a multi-query, multiple sql statements but it is one trip to the database.
Here is an example from the nhibernate documentation:
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/performance.html
IMultiQuery multiQuery = s.CreateMultiQuery()
.Add(s.CreateQuery("from Item i where i.Id > ?")
.SetInt32(0, 50).SetFirstResult(10))
.Add(s.CreateQuery("select count(*) from Item i where i.Id > ?")
.SetInt32(0, 50));
IList results = multiQuery.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Maybe not exactly what you were thinking.