Nhibernate subquery with multiple columns join - c#

I have database structure for Plans and PlanVersions in following relationship:
+------+ +-------------+
| Plan | --------------> | PlanVersion |
+------+ 1 (1..n) +-------------+
PlanVersion is version table tracking all version changes and it have ActiveFromData and ActiveToData columns show us when was this version active.
Plan also can have SubPlans which can change in time so PlanVersion also have ParrentPlanId column which tell us what was current subplan for version.
What i want is to get all changes of all SubPlans since some time and for specific Plan.
This query is what i came with:
DECLARE #since datetime;
set #since = '2014-08-27 12:00:00.000';
DECLARE #parrentPlan bigint;
set #parrentPlan = 1;
SELECT pv.*
FROM [dbo].[PlanVersion] pv
INNER JOIN
/* Query Over subselect*/
(
SELECT PlanId, MAX(ActiveFromDate) AS MaxActiveFromDate
FROM [dbo].[PlanVersion] pv
WHERE pv.ParentPlanId=#parrentPlan
GROUP BY PlanId
) groupedVersions
ON pv.ParentPlanId = groupedVersions.PlanId
AND pv.ActiveFromDate = groupedVersions.MaxActiveFromDate
WHERE (pv.ActiveFromDate>=#since OR pv.ActiveToDate>#since)
Now i want is translate that to Nhibernate QueryOver:
i have this code
var subquery = QueryOver.Of<PlanVersion>()
.Where(x => x.ParentPlan.Id == parrentPlanId)
.Select(
Projections.Group<PlanVersion>(e => e.ParrentPlan.Id),
Projections.Max<PlanVersion>(e => e.ActiveFromDate)
);
But i dont know how to write that inner join on Two columns from suquery in QueryOver.
Notes:
We use Nhibernate 3.3 with 4.0 in testing
This query will be part of polling so performance is really important for me

I would say, that this has solution. We have to use a bit more complex SQL in fact. This approach I've already deeply explained here:
Query on HasMany reference
So, below is just a draft based on your subquery draft. What we are doing, is creating two subselects in fact (check the intended SQL here)
PlanVersion planVersion = null;
// the most INNER SELECT
var maxSubquery = QueryOver.Of<PlanVersion>()
.SelectList(l => l
.SelectGroup(item => item.ParentPlan.Id)
.SelectMax(item => item.ActiveFromDate)
)
// WHERE Clause
.Where(item => item.ParentPlan.Id == planVersion.ParentPlan.Id)
// HAVING Clause
.Where(Restrictions.EqProperty(
Projections.Max<PlanVersion>(item => item.ActiveFromDate),
Projections.Property(() => planVersion.ActiveFromDate)
));
// the middle SELECT
var successSubquery = QueryOver.Of<PlanVersion>(() => planVersion )
// the Plan ID
.Select(pv => pv.ParentPlan.Id)
.WithSubquery
.WhereExists(maxSubquery)
;
having this subqueries, we can ask for plan itself:
// the most outer SELECT
var query = session.QueryOver<Plan>()
.WithSubquery
.WhereProperty(p => p.Id)
.In(successSubquery)
.List<Plan>();
There could be some minor typos, but the draft should give you clear answer how to...

Related

uses First/FirstOrDefault/Last/LastOrDefault operation without OrderBy and filter which may lead to unpredictable results

I have a linq query which gave me the warning but it still works. I want to get rid of the warning.
uses First/FirstOrDefault/Last/LastOrDefault operation without OrderBy and filter which may lead to unpredictable results.
The linq query is
var list = (from u in _db.user
join r in _db.resource on u.userId equals r.userId
join t in _db.team on u.bossId equals t.bossId
where r.pid == pid
select new MyDto
{
pid = pid,
userId = u.userId,
teamId = t.teamId,
name = t.name
}).GroupBy(d => d.userId).Select(x => x.First()).OrderBy(y => y.userId).ToList();
I use EntityFramework Core 2.1
UPDATE:
I changed the code by the comments.
var list = (from u in _db.user
join r in _db.resource on u.userId equals r.userId
join t in _db.team on u.bossId equals t.bossId
where r.pid == pid
select new MyDto
{
pid = pid,
userId = u.userId,
teamId = t.teamId,
name = t.name
})
.GroupBy(d => d.userId)
.Select(x => x.OrderBy(y => y.userId)
.First())
.ToList();
Then there is a different warning.
The LINQ expression 'GroupBy([user].userId, new MyDto() {pid =
Convert(_8_locals1_pid_2, Int16), userId = [user].UserId, .....) could
not be translated and will be evaluated locally.
We have this expression
.Select(x => x.First())
Which record will be first for that expression? There's no way to know, because at this point the OrderBy() clause which follows hasn't processed yet. You could get different results each time you run the same query on the same data, depending on what order the records were returned from the database. The results are not predictable, exactly as the error message said.
But surely the database will return them in the same order each time? No, you can't assume that. The order of results in an SQL query is not defined unless there is an ORDER BY clause with the query. Most of the time you'll get primary key ordering (which does not have to match insert order!), but there are lots of things that can change this: matching a different index, JOIN to a table with a different order or different index, parallel execution with another query on the same table + round robin index walking, and much more.
To fix this, you must call OrderBy() before you can call First().
Looking a little deeper, this is not even part of the SQL. This work is happening on your client. That's not good, because any indexes on the table are no longer available. It should be possible to do all this work on the database server, but selecting the first record of a group may mean you need a lateral join/APPLY or row_number() windowing function, which are hard to reproduce with EF. To completely remove all warnings, you may have to write a raw SQL statement:
select userId, teamId, name, pid
from (
select u.userId, t.teamId, t.name, r.pid, row_number() over (order by u.userId) rn
from User u
inner join resource r on r.userId = u.userId
inner join team t on t.bossId = u.bossId
where r.pid = #pid
) d
where d.rn = 1
Looking around, it is possible to use row_number() in EF, but at this point I personally find the SQL much easier to work with. My view is ORMs don't help for these more complicated queries, because you still have to know the SQL you want, and you also have to know the intricacies of the ORM in order to build it. In other words, the tool that was supposed to make your job easier made it harder instead.

Nhibernate 3.0 IQueryOver group by and fetch one entire record in each group which satisfy the condition

My requirement is to fetch the SiteItemOnHand records, group by Businessunitid,inventoryitemid and get the whole SiteItemOnHand record in each group which is having max(lastmodifiedtimestamp)
I am trying to write nhibernate equivalent query for the below sql query
Select x.* from
(
Select businessunitid,inventoryitemid,max(lastmodifiedtimestamp) as maxvalue from InventoryItemBUOnHand
group by businessunitid,inventoryitemid HAVING inventoryitemid in (939) and businessunitid=829
) as x
inner join
(
Select businessunitid,inventoryitemid,lastmodifiedtimestamp,inventoryitemonhandid from InventoryItemBUOnHand
where inventoryitemid in (939) and businessunitid=829
) as y
on x.businessunitid=y.businessunitid and x.inventoryitemid =y.inventoryitemid and x.maxvalue=y.lastmodifiedtimestamp
I have so many limitations. I am only allowed to use Nhibernate 3.0 version and also writing sql queries (Criteria) strictly prohibited.
I was able to write only half part of the query which is below. Any help would be appreciated.
var query= _session.QueryOver<SiteItemOnHand>()
.Where(x => x.Businessunitid == siteID)
.Where(x => x.End == endDate)
.Where(x => x.CountID != filterCount.ID)
.WhereRestrictionOn(x => x.ItemID).IsIn(itemIdList.ToArray())
.SelectList(list => list
.SelectMax(x => x.LastModifiedTimestamp)
.SelectGroup(x => x.Businessunitid)
.SelectGroup(x => x.ItemId));
The above query is generating the below SQL Query which only returns the three columns, but i need entire record because after retriving the record i need to update. I need to fetch all the SiteItemOnHand records which satisfies the above query.
SELECT max(this_.lastmodifiedtimestamp) as y0_, this_.businessunitid as y1_, this_.inventoryitemid as y2_
FROM InventoryItemBUOnHand this_
WHERE this_.businessunitid = 567 and this_.enddate = '1/31/2019 1:18:17 AM' and not (this_.itemcountid = 958)
and this_.inventoryitemid in (744,755)
GROUP BY this_.businessunitid, this_.inventoryitemid
Any help would be appreciated. Please let me know if you need more information.
If I understand you correctly you have data like this and you want the marked records. I don't know if iventoryitemonhandid is the id of the table, so I'm not relying on that.
Table InventoryItemBUOnHand (mapped as SiteItemOnHand)
| businessunitid|inventoryitemid|lastmodifiedtimestamp|inventoryitemonhandid|
| 829| 939| 2019-01-01 00:00:00| 100| <--
| 829| 940| 2019-01-02 00:00:00| 101|
| 829| 940| 2019-01-03 00:00:00| 102| <--
| 829| 950| 2019-01-04 00:00:00| 103|
| 829| 950| 2019-01-10 00:00:00| 104| <--
| 829| 950| 2019-01-06 00:00:00| 105|
If yes, then I'd use a SubQuery like in this question:
SO: NHibernate - Select full records from aggregates
Modified (not tested) to your requirements it would look something like this:
int siteID = 829;
List<int> itemIdList = new List<int>() { 939, 940, 950 };
SiteItemOnHand siAlias = null;
var subQuery = QueryOver.Of<SiteItemOnHand>()
.Where(x => x.Businessunitid == siAlias.Businessunitid)
.And(x => x.ItemID == siAlias.ItemID)
.Select(Projections.Max<SiteItemOnHand>(y => y.lastmodifiedtimestamp));
var siteItems = Session.QueryOver<SiteItemOnHand>(() => siAlias)
.Where(x => x.Businessunitid == siteID)
.AndRestrictionOn(x => x.ItemID).IsIn(itemIdList.ToArray())
.WithSubquery.Where(x => siAlias.lastmodifiedtimestamp == subQuery.As<DateTime>())
.List();
The goal here is to use a Subquery to filter the correct max dates per group and then use that to filter the actual records.
The resulting SQL would look like this:
SELECT <field_list> FROM InventoryItemBUOnHand
WHERE Businessunitid = 829
AND inventoryitemid in (939, 940, 950)
AND this_.lastmodifiedtimestamp =
(SELECT max(this_0_.lastmodifiedtimestamp) as y0_
FROM InventoryItemBUOnHand this_0_
WHERE this_0_.Businessunitid = this_.Businessunitid and this_0_.ItemID = this_.ItemID)
Caution: Comparing lastmodifiedtimestamp may cause unwanted results when two records have the same value for the same businessunitid and inventoryitemid. If that can happen, you can add an OrderBy and select the first record only. It becomes even simpler if inventoryitemonhandid is a unique index.

NH QueryOver - use properties of main query in subquery

I am trying to convert following SQL to QueryOver:
Select 1
From myTable mt
Where mt.ForeignKey in (select ID from otherTable ot where ot.ID = R.ID)
I want to use this subquery inside an EXISTS / NOT EXISTS statement like:
select * from table R where .... AND EXISTS (query above)
Currently I have something like:
mainQuery.WithSubquery.WhereExists(QueryOver.Of<myTable>()
.Where(mt => mt.ForeignKey)
.WithSubquery.IsIn(QueryOver.Of<otherTable>().Where(c => c.Id == R.SomeId)));
I created this query as a subquery which I want to connect to the main query.
The problem is that the table aliased as R is the table called by the main query and I don´t know how to access columns of the table (NHibernate Model) R (which is not accesible in the query above), so my question is:
How can I get values from the main query and use them in a subquery. I think this is only possible by creating the subquery inline (as in mainQuery.WithSubquery.Where(..) or smth. similar) but I can´t see what would be the best possible way to do so. I appreciate any help!
Thanks in advance!
The trick is to use proper alias, for the parent query:
// the alias
myTable R = null;
mainQuery
.WithSubquery
.WhereExists(QueryOver
.Of<myTable>( () => R) // the Alias in place
.Where(mt => mt.ForeignKey)
.WithSubquery.IsIn(QueryOver.Of<otherTable>().Where(c => c.Id == R.SomeId)));
Note, not fully sure about the mainQuery part, but the solution in general here is like this:
// I. the outer query ALIAS
Employee emplyoee = null;
// II. the subquery - using the alias
var subQuery = QueryOver.Of<Contact>()
.Select(x => x.ID)
.Where(x => x.Related.ID == emplyoee.ID); // use alias
// III. declare the outer query and use the above alias
var query = session.QueryOver<Employee>(() => emplyoee) // declare alias
.WithSubquery
.WhereExists(subQuery); // put both together
Also check this for more ideas

How to write an linq statement to get the last of a group of records

I have 2 SQL statements that basically do the same thing, that is, retrieve the last record from a table based on a datetime field for a group of records. I am using the data-first Entity Framework model. How would I write either of these SQL statements using LINQ Lambda functions?
ie,
var u = db.AccessCodeUsage.Where(...).GroupBy(...)
rather than
var u = from a in db.AccessCodeUsage
where ...
group by ...
SQL Statements:
SELECT *
FROM AccessCodeUsage a
WHERE NOT EXISTS (SELECT 1
FROM AccessCodeUsage
WHERE LocationId = a.LocationId
AND Timestamp > a.Timestamp)
SELECT a.*
FROM AccessCodeUsage a
WHERE a.Timestamp =
(SELECT MAX(Timestamp)
FROM AccessCodeUsage
WHERE a.LocationId = LocationId
AND a.AccessCode = AccessCode
GROUP By LocationId, AccessCode)
If you need to have the method-call form, but are finding it tricky to work out, then use the other syntax first:
from a in db.AccessCodeUsage
orderby a.TimeStamp descending
group a by a.LocationId into grp
from g in grp select g.First();
Then convert to method calls by taking each clause one at a time:
db.AccessCodeUsage
.OrderByDescending(a => a.TimeStamp)
.GroupBy(a => a.LocationId)
.Select(g => g.First());
From which I can workout the second without bothering to write out the linq-syntax form first:
db.AccessCodeUsage
.OrderByDescending(a => a.TimeStamp)
.GroupBy(a => new {a.LocationId, a.AccessCode})
.Select(g => g.First());
(Except it doesn't include what may be a bug, in that if timestamps aren't guaranteed unique, the SQL given in the question could include some extra inappropriate results).
I can't check on the SQL produced right now, but it should hopefully be equivalent in results (if not necessarily matching). There's cases where grouping doesn't translate to SQL well, but I certainly don't think this would be one.
I ended up using the following which corresponds to the first SQL statement.
// Retrieve only the latest (greatest value in timestamp field) record for each Access Code
var last = AccessCodeUsages.Where(u1 => !AccessCodeUsages
.Any(u2 => u2.LocationId == u1.LocationId &&
u2.AccessCode == u1.AccessCode &&
u2.Timestamp > u1.Timestamp));

Joining and grouping multiple tables with LINQ when using a linking table

I'm looking for some advice on how best to get the first record when using a join with multiple tables, as demonstrated below.
I have three tables:
Leads <-- this should be unique in the results
LeadAddresses (joining
table)
Addresses
Normally I'd join them like this:
from t2
in db.Leads
.Where(o => t1.LeadId == o.Lead_ID)
from t4
in db.LeadAddresses
.Where(o => t2.Lead_ID == o.Lead_ID)
.DefaultIfEmpty()
from t5
in db.Addresses
.Where(o => t4.Address_ID == o.Address_ID)
.DefaultIfEmpty()
(if this is bad practice, let me know ;)
I'm looking to get a property from the Addresses table (the one with, say, the maximum ID) for each Lead record and project to a model:
select new LeadGridModel
{
...
});
Example:
Lead Company | City | ZIP
==============================
Company 1 | Boston | 00000
Company 2 | Houston | 00001
from l in db.Leads
from a in l.LeadAddresses.Select(la => la.Address).OrderByDescending(a => a.ID).Take(1).DefaultIfEmpty()
select new { l, a }
This might look tricky, but you understand it part by part:
Using OrderByDescending in combination with Take(1) we take the address with the maximum ID
Using DefaultIfEmpty we create a left-join.
Be aware that this pattern forces a loop-join due to limitation of SQL Server. For small result sets this is usually not a problem.

Categories

Resources