问题
I have the following entities:
public class Shift
{
public virtual int ShiftId { get; set; }
public virtual string ShiftDesc { get; set; }
public virtual IList<ShiftHistory> ShiftHistory { get; set; }
}
public class ShiftHistory
{
public virtual System.DateTime ShiftStartLocal { get; set; }
public virtual System.DateTime ShiftEndLocal { get; set; }
public virtual Zone Zone { get; set; }
public virtual Shift Shift { get; set; }
public virtual int RowId { get; set; }
}
public class Zone
{
public virtual int EntId { get; set; }
public virtual string EntName { get; set; }
}
A shift is a shift definition, zone is a place where people work and shift history is history data of when a shift was run at a zone.
What I need is to get the last time a shift was run for all zones.
/* EDIT: Sorry, I was vague. Every shift can have a history at multiple zones. What I need is the latest history irrespective of the zone, for each shift, in any NH query type. */
But this is not so simple, at least not for me now, and I've spend too much time on this, and have finally resorted to doing a sql query:
public class GetAllShiftSchedules : AbstractQueryObject<IList<ShiftScheduleResult>>
{
public override IList<ShiftScheduleResult> GetResult()
{
//use distinct because of the zones - if a shift is run at multiple zones, it ends at the same time, no? :)
var sql =
@"
select distinct
sh.ShiftId, s.ShiftDesc, sh.ShiftStartLocal, sh_inner.LatestEndTimeLocal
from ShiftHistory sh
inner join Shift s on s.ShiftId = sh.ShiftId
inner join
(
select ShiftId, max(ShiftEndLocal) as LatestEndTimeLocal from ShiftHistory group by ShiftId
)
sh_inner on sh_inner.ShiftId = sh.ShiftId and sh_inner.LatestEndTimeLocal = sh.ShiftEndLocal
";
var res = this.Session.CreateSQLQuery(sql).List().OfType<object[]>().Select(p => new ShiftScheduleResult(p)).ToList();
return res;
}
}
public class ShiftScheduleResult
{
public int ShiftId { get; private set; }
public string ShiftDesc { get; private set; }
public DateTime LastShiftStartLocal { get; private set; }
public DateTime LastShiftEndLocal { get; private set; }
internal ShiftScheduleResult(object[] data)
{
this.ShiftId = (int)data[0];
this.ShiftDesc = (string)data[1];
this.LastShiftStartLocal = (DateTime)data[2];
this.LastShiftEndLocal = (DateTime)data[3];
}
}
The closest I got was having:
var innerSubquery =
QueryOver.Of<ShiftHistory>()
.Select(
Projections.Group<ShiftHistory>(e => e.Shift),
Projections.Max<ShiftHistory>(e => e.ShiftEndLocal));
IList<Shift> shifts =
this.Session.QueryOver<Shift>()
.JoinQueryOver<ShiftHistory>(p => p.ShiftHistory)
.Where(Subqueries.WhereProperty<ShiftHistory>(p => p.ShiftEndLocal).Eq(innerSubquery)).List();
Which generated the following sql:
SELECT
...
FROM
Shift this_
inner join ShiftHistory shifthisto1_ on this_.ShiftId=shifthisto1_.ShiftId
WHERE
shifthisto1_.ShiftEndLocal =
(
SELECT this_0_.ShiftId as y0_, max(this_0_.ShiftEndLocal) as y1_ FROM ShiftHistory this_0_ GROUP BY this_0_.ShiftId
)
Which of course fails because the group by select returns multiple values and we can't compare this to ShEndLoc. This would actually work for me, if I could get the grouping projection to return only the max date. However, to make it complete, we should also be able to compare by shift id.
I am fine with returning the DTO but having an actual shift or a shift history included would be cool.
Thanks for reading.
回答1:
var list =
this.Session.Query<Shift>().Select(
p => new
{
Shift = p,
LastShiftHistory =
this.Session.Query<ShiftHistory>()
.Where(sh => sh.Shift == p)
.OrderByDescending(sh => sh.ShiftEndLocal)
.Select(sh => sh)
.FirstOrDefault()
})
.ToList();
should work. It would be better though to project to a DTO, since the subquery will lazy load.
回答2:
Something like this should work... Your result is annonymous type because of combination of caluses form different classes...
public class QueryPlace()
{
List<ShiftHistory> ShiftHistrys = new List<ShiftHistory>();
List<Shift> Shifts = new List<Shift>();
List<Zone> Zones = new List<Zone>();
public object MakeQuery()
{
var mshift = (from r in (from sh in session.Query<ShiftHistory>() from sf in session.Query<Shift>() where sh.Shift == sf
select new{sh.ShiftStartLocal, sh.ShiftEndLocal, sf.ShiftId, sf.ShiftHistory}) group r by r.ShiftId into g
select new {ShiftStartLocal = g.Max(s => s.ShiftStartLocal), ShiftEndLocal = g.Max(s => s.ShiftEndLocal), ShiftId = g.Key}).FirstOrDefault();
return mshift;
}
}
回答3:
Just for completeness, because this db of mine uses composite id's I wasn't able to get the full query gunteman provided.
What I ended up having is my original query, written in NH:
var list =
this.Session.Query<Shift>()
.Select(
p =>
new ShiftScheduleResult()
{
ShiftId = p.ShiftId,
ShiftDesc = p.ShiftDesc,
LastShiftStartLocal
= p.ShiftHistory
.OrderByDescending(sh => sh.ShiftEndLocal)
.Select(x => x.ShiftStartLocal)
.FirstOrDefault(),
LastShiftEndLocal
= p.ShiftHistory
.OrderByDescending(sh => sh.ShiftEndLocal)
.Select(x => x.ShiftEndLocal)
.FirstOrDefault()
})
.ToList();
I believe that also Zoran's query would work. Thank you all for your help.
回答4:
When the DB scheme combined with the business needs result in complicated queries, changing the DB scheme could be better. By way of example, adding an "active" flag on your ShiftHistory, or adding a foreign key in your Shift on the last ShiftHistory. Of course this could be considered as some "de-normalization", with possibility of having incoherent data in DB (more than one active history for the same Shift, or a "Last shift" foreign key not pointing on the real last one). But would handling those risks cost you more than having a complicated query to maintain ? It is a trade-off to consider I think.
来源:https://stackoverflow.com/questions/14628860/nhibernate-group-by-join-query