I am trying to fetch several entities without having a single root entity ( the directed graph of entity-relations is only a weakly connected graph, not strongly connected) and I cant figure out how to do it in Nhibernates QueryOver api (or Linq, but that seems to be even weaker)
These are the relations I have:
ClientTaxEntity references 1 Client and N Manufacturers
InstructionTemplate references 1 Client and 1 Manufacturer
I want to get a result with all possible Client-Manufacturer pairs (possible = they are together within a ClientTaxEntity) and fetch a Template to them if it exists (null otherwise)
This is what I have tried so far:
Client client = null;
Manufacturer manufacturer = null;
InstructionTemplate template = null;
ClientTaxEntity taxEntity = null;
InstructionTemplateInfo info = null;
var query =Session.QueryOver<ClientTaxEntity>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.Left
.JoinQueryOver(() => template, () => template,() => template.Client == client && template.Manufacturer == manufacturer);
var result = query
.SelectList(builder => builder
.Select(() => client.Name).WithAlias(() => info.ClientName)
.Select(() => client.Id).WithAlias(() => info.ClientId)
.Select(() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.Select(() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.Select(() => template.Id).WithAlias(() => info.TemplateId)
.Select(() => template.Type).WithAlias(() => info.Type)
)
.TransformUsing(Transformers.DistinctRootEntity)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.List<InstructionTemplateInfo>();
The info object is the result I would like.
However the syntax .JoinQueryOver(() => template does not seem to be valid for a path parameter (exception says : could not resolve property: template of: ClientTaxEntity
To get the result you want when writing a query in SQL it would be necessary to write something along the lines of:
SELECT Client_Id, Client_Name, Manufacturer_Id, Manufacturer_Name
FROM
(
SELECT Client.Id as Client_Id, Client.Name as Client_Name,
Manufacturer.Id as Manufacturer_Id, Manufacturer.Name as Manufacturer_Name
FROM ClientTax
INNER JOIN Client on Client.Id = ClientTax.Client_Id
INNER JOIN Manufacturer on Manufacturer.Id = Manufacturer.Manufacturer_id
UNION
SELECT Client.Id, Client.Name, Manufacturer.Id, Manufacturer.Name
FROM InstructionTemplate
INNER JOIN Client on Client.Id = InstructionTemplate.Client_Id
INNER JOIN Manufacturer on Manufacturer.Id = InstructionTemplate.Manufacturer_id
) a
GROUP BY Client_Id, Client_Name, Manufacturer_Id, Manufacturer_Name;
Unfortunately converting such a query to one of NHibernate's query APIs is not possible because NHibernate does not support the UNION statement*. See this question and the feature request NH-2710 in NHibernate's bug tracker.
*Except when using union-subclass. See the docs for further details
The only options I can see are
Perform an SQL Query and map this to a DTO
public class InstructionTemplateInfo
{
public int Client_Id { get; set; }
public string Client_Name { get; set; }
public int Manufacturer_Id { get; set; }
public string Manufacturer_Name { get; set; }
}
then
var result = session
.CreateSQLQuery(theSQLQueryString)
.SetResultTransformer(Transformers.AliasToBean<InstructionTemplateInfo>())
.List<InstructionTemplateInfo>();
Create a view in the database and map this like a normal entity.
If the DBMS supports multiple results sets, like SQL Server, you could write two queries but mark them both as Future and then merge the two results set in code, i.e.
var resultSet1 = Session.QueryOver<ClientTaxEntity>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.SelectList(builder => builder
.SelectGroup((() => client.Name).WithAlias(() => info.ClientName)
.SelectGroup((() => client.Id).WithAlias(() => info.ClientId)
.SelectGroup((() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.SelectGroup((() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.Future<InstructionTemplateInfo>;
var resultSet2 = Session.QueryOver<InstructionTemplate>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.SelectList(builder => builder
.SelectGroup((() => client.Name).WithAlias(() => info.ClientName)
.SelectGroup((() => client.Id).WithAlias(() => info.ClientId)
.SelectGroup((() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.SelectGroup((() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.Future<InstructionTemplateInfo>;
var result = resultSet1.Concat(resultSet2 )
.GroupBy(x=>x.ClientId+"|"+x.ManufacturerId)
.Select(x=>x.First());
The advantage of this approach is that the DBMS will only be hit once.
See Ayende's blog post for further details about this feature.
Related
Please need some help converting this sql query to nhibernate
select a.ID, count(b.ID)
from appusers a
left join weeklytasks b on a.ID = b.TaskOwner and b.taskstatus = 1
group by a.ID
It's difficult to answer without knowing your entities, mappings, used technology(ICreteria API, QueryOver, Linq).
But I can suggest this solution using QueryOver:
AppUser ownerAlias = null;
WeeklyTask taskAlias = null;
var result = Session.QueryOver(() => taskAlias)
.JoinAlias(x => x.TaskOwner,
() => ownerAlias,
NHibernate.SqlCommand.JoinType.RightOuterJoin,
Restrictions.Where(() => taskAlias.Status == 1))
.SelectList(list => list
.SelectGroup(x => ownerAlias.Id)
.SelectCount(x => x.Id))
.List<object[]>();
or this:
var result = Session.QueryOver<WeeklyTask>()
.Where(x => x.Status == 1)
.Right.JoinQueryOver(x => x.TaskOwner)
.SelectList(list => list
.SelectGroup(x => x.TaskOwner.Id)
.SelectCount(x => x.Id))
.List<object[]>();
Please notice that in this approach your WeeklyTask entity must contains mapped reference to AppUser entity.
I tired this
respondentSanctionSubquery = respondentSanctionSubquery.Select(x => x.Respondent.Incident.Id);
but i got this exception :
i have 3 entities not 2 entities :
class Respondent
{
public IncidentObj{get;set;}
}
class Incident
{
public int Id{get;set;}
}
class RespondentSanction
{
public Respondent RespondentObj{get;set;}
}
You have to join other entities also to the main query (as follows),
X x = null;
Respondent respondent = null;
Incident incident = null;
respondentSanctionSubquery = respondentSanctionSubquery
.JoinQueryOver(() => x.Respondent , () => respondent)
.JoinQueryOver(() => respondent.Incident , () => incident )
.Select(r => incident.Id);
or else you might want to go for subqueries,
X x = null;
Respondent respondent = null;
Incident incident = null;
var subQuery = (QueryOver<Respondent>)session.QueryOver<Respondent>(() => respondent)
.JoinQueryOver(() => respondent.Incident , () => incident )
.Where(() => respondent.Id == x.Respondent.Id)
.Select(r => incident.Id);
var query = session.QueryOver(() => x)
.SelectList(l => l.SelectSubQuery(subQuery));
You have to do a JOIN in order to do a projection like that:
respondentSanctionSubquery =
respondentSanctionSubquery
.JoinQueryOver(x => x.RespondentObj)
.JoinQueryOver(resp => resp.IncidentObj)
.Select(inc => inc.Id);
you should do join between the entities using Join alias
respondentSanctionSubquery =
respondentSanctionSubquery
.JoinAlias(x => x.RespondentObj)
.JoinAlias(resp => resp.IncidentObj)
.Select(inc => inc.Id);
for more information please check this URL :What is the difference between JoinQueryOver and JoinAlias?
Assuming I have three simple tables
schedule
{
Student student { get; set;}
Teacher teacher { get; set;}
bool Deleted { get; set; }
}
Student
{
string Name { get; set; }
IList<schedule> TeacherMeetings {get; set; }
}
and assume teacher has the same thing, name and list of student schedules.
I want to select, from schedule, the list of all student names for a particular teacher. I can write the query for conditions and everything, but having trouble selecting just the student names.
Here is my current query:
DetachedCriteria dc = QueryOver.Of<Schedule>(() => sAlias)
.JoinAlias(() => sAlias.student, () => studentAlias)
.JoinAlias(() => sAlias.teacher, () => teacherAlias)
.Where(() => teacherAlias.MembershipGuid == teacherGuid)
.AndNot(() => sAlias.isDeleted)
//.Select(() => studentAlias.Name)
.SelectList(list => list
.SelectGroup(x => x.student.Name).WithAlias(() => studentAlias.Name))
.DetachedCriteria
;
If I comment out all select, profiler shows the query being generated as the right one, with right joins on right tables, only, it selects all columns on all tables.
I can't seem to get the select right, I only need the student.Name only.
How do I write this please? I've been at this for over an hour trying out different things but it is always the select that errors out, saying NHibernate.QueryException: could not resolve property and that studentName isn't recognizable.
thanks.
If you just want a list of strings, you shouldn't need to use WithAlias. WithAlias is used to project that column into a member on a result object.
Something like this should work:
DetachedCriteria dc = QueryOver.Of<Schedule>(() => sAlias)
.JoinAlias(() => sAlias.student, () => studentAlias)
.JoinAlias(() => sAlias.teacher, () => teacherAlias)
.Where(() => teacherAlias.MembershipGuid == teacherGuid)
.AndNot(() => sAlias.isDeleted)
.SelectList(list => list
.Select(() => studentAlias.Name))
.DetachedCriteria;
So the following should get you a list of strings:
IList<string> names = dc.GetExecutableCriteria(session)
.List<string>();
Update (per comment). Here's how you would order by student name, descending:
DetachedCriteria dc = QueryOver.Of<Schedule>(() => sAlias)
.JoinAlias(() => sAlias.student, () => studentAlias)
.JoinAlias(() => sAlias.teacher, () => teacherAlias)
.Where(() => teacherAlias.MembershipGuid == teacherGuid)
.AndNot(() => sAlias.isDeleted)
.SelectList(list => list
.Select(() => studentAlias.Name))
.OrderBy(() => studentAlias.Name).Desc()
.DetachedCriteria;
How to do the following join to return Users who have access to a Company given a company id.
The problem is there is no explicit relationship using a User object between UserAccess and User they simply join on the string property Username:
User(Username, Name)
UserAccess(Username, Company)
Company(Id)
Session.QueryOver<Company>()
.Where(c => c.Id == companyId)
.JoinQueryOver<UserCompanyAccess>(u => u.UserAccessList)
.JoinQueryOver<User>(u => **Nope no property, just a string**
could be done with a subquery
var subquery = QueryOver.Of<Company>()
.Where(c => c.Id == companyId)
.JoinQueryOver<UserCompanyAccess>(u => u.UserAccessList)
.Select(uca => uca.UserName);
var users = session.QueryOver<User>()
.WithSubquery.WhereProperty(u => u.Name).In(subquery)
.List();
As of 5.1.0, it is possible to make hibernate generate an actual sql join on an undeclared (unmapped) relationship. E.g. all orders sorted by customer's spending:
var criteria = _session
.CreateCriteria<Order>("order");
criteria
.CreateEntityAlias(
"customer",
Restrictions.EqProperty("order.customerId", "customer._id"),
JoinType.LeftOuterJoin,
typeof(Customer).FullName)
.AddOrder(new Order("customer._lifetimeSpending", ascending:false));
return criteria.List<Order>();
Also possible with QueryOver (sample from NHibernate docs):
Cat cat = null;
Cat joinedCat = null;
var uniquelyNamedCats = sess.QueryOver<Cat>(() => cat)
.JoinEntityAlias(
() => joinedCat,
() => cat.Name == joinedCat.Name && cat.Id != joinedCat.Id,
JoinType.LeftOuterJoin)
.Where(() => joinedCat.Id == null)
.List();
I am trying take this sql query and make it into an nhibernate HQL query. I am using nhibernate 3 and Fluent Nhibernate 1.2
SELECT dbo.Tasks.CourseId, dbo.CoursePermissions.BackgroundColor, dbo.Tasks.DueDate, dbo.Tasks.TaskName, dbo.Tasks.TaskId
FROM dbo.Courses INNER JOIN
dbo.Tasks ON dbo.Courses.CourseId = dbo.Tasks.CourseId INNER JOIN
dbo.CoursePermissions ON dbo.Courses.CourseId = dbo.CoursePermissions.CourseId
WHERE (dbo.Tasks.CourseId = 1)
I would have liked to use linq but I don't think nhibernate supports linq joins yet so I guess I am stuck with using HQL(unless someone knows a better way).
I guess I can use QueryOver or the other ways nhibernate does queries so whatever works the best. I still don't understand the difference between all the ways as if I could do everything in linq I would.
However I have no clue on how to write my query.
Thanks
Edit
I now have this(changed a bit)
Course cAlias = null;
Task tAlias = null;
CoursePermission cpAlias = null;
var result = session.QueryOver<Task>(() => tAlias)
.JoinAlias(() => tAlias.Course, () => cAlias)
.JoinAlias(() => cAlias.CoursePermissions, () => cpAlias)
.Where(Restrictions.In(Projections.Property(() => cAlias.Id), courseIds))
.And(x => x.DueDate >= startDate)
.And(x => x.DueDate <= endDate)
.Select( Projections.Property(() => cAlias.Id),
Projections.Property(() => cpAlias.BackgroundColor),
Projections.Property(() => tAlias.DueDate),
Projections.Property(() => tAlias.TaskName),
Projections.Property(() => tAlias.TaskId))
.List<object[]>();
I know want to map it to
public class TaskAppointments
{
public int Id { get; set; }
public string BackgroundColor { get; set; }
public DateTime DueDate { get; set; }
public int TaskId { get; set; }
public string TaskName { get; set; }
}
How do I do this. If this was a linq method I would do
.Select(new TaskAppointments { TaskId = Projections.Property(() => tAlias.TaskId)})
but it says it can't convert it to an int.
Edit2
This is what I came up with
Course cAlias = null;
Task tAlias = null;
CoursePermission cpAlias = null;
TaskAppointments taskAppointments = null;
List<TaskAppointments> result = session.QueryOver<Task>(() => tAlias)
.JoinAlias(() => tAlias.Course, () => cAlias)
.JoinAlias(() => cAlias.CoursePermissions, () => cpAlias)
.Where(Restrictions.In(Projections.Property(() => cAlias.Id), courseIds))
.And(x => x.DueDate >= startDate)
.And(x => x.DueDate <= endDate)
.SelectList(list =>
list.SelectGroup(x => x.TaskId).WithAlias(() => taskAppointments.TaskId)
.SelectGroup(() => cpAlias.BackgroundColor).WithAlias(() => taskAppointments.BackgroundColor)
.SelectGroup(x => x.DueDate).WithAlias(() => taskAppointments.DueDate)
.SelectGroup(x => x.TaskName).WithAlias(() => taskAppointments.TaskName)
)
.TransformUsing(Transformers.AliasToBean<TaskAppointments>())
.List<TaskAppointments>().ToList();
Without mappings I assume that you have the following relationships: Courses -> Tasks (1:n) and Courses -> CoursePermissions (1:n)
I also assumed that you do not want the complete objects but only certain properties, so I used projections.
QueryOver version:
// the aliases are required here, so that we can reference the entities properly
Courses cAlias = null;
Tasks tAlias = null;
CoursePermissions cpAlias = null;
var result = session.QueryOver<Courses>(() => cAlias)
.JoinAlias(() => cAlias.Tasks, () => tAlias)
.JoinAlias(() => cAlias.CoursePermissions, () => cpAlias)
.Where(() => cAlias.CourseId == 1)
.Select(Projections.Property(() => cAlias.CourseId),
Projections.Property(() => cpAlias.BackgroundColor),
Projections.Property(() => tAlias.DueDate),
Projections.Property(() => tAlias.TaskName),
Projections.Property(() => tAlias.TaskId))
.List<object[]>();
Edit start
If you need to do a WHERE IN clause, you can do this:
List<int> courseIdList = new List<int>() { 1, 2 };
var result = session.QueryOver<Courses>(() => cAlias)
.JoinAlias(() => cAlias.Tasks, () => tAlias)
.JoinAlias(() => cAlias.CoursePermissions, () => cpAlias)
.Where(Restrictions.In(Projections.Property(() => cAlias.CourseId), courseIdList))
.Select(...)
.List<object[]>();
Edit end
Edit 2 start
If you want to transform it into a DTO:
// AliasToBeanResultTransformer is in namespace NHibernate.Transform
// we have to use .As("...") for the transformer to find the correct property-names
var result = ...
.Select(Projections.Property(() => cAlias.CourseId).As("CourseId"),
Projections.Property(() => cpAlias.BackgroundColor).As("BackgroundColor"),
Projections.Property(() => tAlias.DueDate).As("DueDate"),
Projections.Property(() => tAlias.TaskName).As("TaskName"),
Projections.Property(() => tAlias.TaskId).As("TaskId"))
.TransformUsing(new AliasToBeanResultTransformer(typeof(TaskAppointments)))
.List<TaskAppointments>();
Edit 2 end
HQL version:
string hql = "select c.CourseId, cp.BackgroundColor, t.DueDate, t.TaskName, t.TaskId"
+ " from Courses as c inner join c.Tasks as t inner join c.CoursePermissions as cp"
+ " where c.CourseId = 1";
var result2 = session.CreateQuery(hql)
.List<object[]>();
Be aware that this will result in a cartesian product, so for each Course you will get Tasks.Count + CoursePermissions.Count rows.