Nhibernate condition on join with IQueryOver - nhibernate

I was searching for similar problem on google and stackoverflow for almost 2 hours but did not find any solution.
I have 2 tables with relation 1 to many.
1) [Accounts]
PK Account_Id
int User_ID
2) [Temporary_Accounts]
Fk Account_Id
char IsAccepted {'1','0',null}
varchar name
And 2 mapped classes
1) Acc
int Id;
User user;
TempAcc Temp; //cause each Account can have 0 or one TempAcc (with IsAccepted == null)
2)TempAcc
int Id;
bool IsAccepted;
string name;
I want to display all accounts for given user_id with additional information(f.e name) for Accounts which has record in [Temporary_Accounts] and IsAccepted == null.
so the SQL should look like:
select acc.Account_Id, acc.User_Id, tempacc.Name
from Account acc left join Temporary_Account tempacc
on (acc.Account_ID = tempacc.Account_Id and tempacc.IsAccepted is null)
where (acc.User_Id = 65);
but my IQueryOverquery:
IQueryOver<Acc> query = (...)
query.JoinAlias(f => f.Temp,
() => Temp,
JoinType.LeftOuterJoin)
.Where(f => f.Temp.IsAccepted == null)
.And(f => f.user.id == userid);
generates such sql:
select acc.Account_Id, acc.User_Id, tempacc.Name
from Accounts acc left join Temporary_Accounts tempacc
on (acc.Account_ID = tempacc.Account_Id)
where (acc.User_Id = 65 and tempacc.IsAccepted is null);
so I am getting less results than in first correct query.
Do you have any Idea what should I change or what could I do to obtain results from first query ? My Idea was to leftjoin Accounts table with subquery which selects all IsAccepted=null accounts from Temporary_Accounts table ,but I am not sure how to do it in Iqueryover or Icriteria.
I will be grateful for any advices

Since you have a 1-Many between Acc and Temp your sample sql will produce a Cartesian product.
The Queryover you will need uses a Subquery and looks something like the following:
Acc accountAlias = null;
var subQuery = QueryOver.Of<Temp>()
.Where(x=>x.IsAccepted==null)
.And(x=>x.Account.Id==accountAlias.Id);
var results = session.QueryOver<Acc>(()=>accountAlias)
.Where(x=>x.User.Id==65)
.WithSubquery.WhereExists(subQuery);
Producing SQL like this:
select *
from Accounts a
where a.User_Id=65
and exists (
select t.Account_Id
from Temporary_Accounts t
where t.IsAccepted is null and t.Account_Id=a.Account_Id
)
This article on nhibernate.info is very helpful for figuring out complex queries with QueryOver.
UPDATE:
If you need to also find Accounts which do not have any corresponding rows in Temporary_Accounts then you need two subqueries and a Disjunction.
Acc accountAlias = null;
var hasTempAccount = QueryOver.Of<Temp>()
.Where(x=>x.IsAccepted==null)
.And(x=>x.Account.Id==accountAlias.Id);
var doesNotHaveTempAccount = QueryOver.Of<Temp>()
.And(x=>x.Account.Id==accountAlias.Id);
var results = session.QueryOver<Acc>(()=>accountAlias)
.Where(x=>x.User.Id==65)
.Where(Restrictions.Disjunction()
.Add(Subqueries.WhereExists(hasTempAccount))
.Add(Subqueries.WhereNotExists(doesNotHaveTempAccount))
);
UPDATE 2:
Since NH 3.2 you can add extra conditions to a JOIN. See this answer for further details: Adding conditions to outer joins with NHibernate ICriteria/QueryOver query
Temp tempAlias = null;
Account accountAlias = null;
dto dto = null;
var results = Session.QueryOver<Account>(()=>accountAlias)
.JoinAlias(x=>x.TempAccounts,()=>tempAlias,JoinType.LeftOuterJoin,
Restrictions.IsNull(Projections.Property(()=>tempAlias.IsAccepted))
)
.Where(x=>x.Account.Id==65)
.SelectList(list=>list
.Select(()=>accountAlias.Id).WithAlias(()=>dto.AccountId)
.Select(()=>accountAlias.User.Id).WithAlias(()=>dto.UserId)
.Select(()=>tempAlias.Name).WithAlias(()=>dto.TempAccName)
)
.SetResultTransformer(Transformers.AliasToBean<dto>())
.List<dto>();

Related

Query on other query linq

Just assume a simple example:I have two table and use this query:
select *
from People
where PersonGuid = (select PersonGuid from Sellers where Guid = '')
How can I write this query with linq?
I tried this:
var person = from p in loginContext.Person where p.PersonGuid =
(loginContext.Seller.Where(s => s.Guid == sellerGuid).FirstOrDefaultAsync());
but that's wrong.
What is the right way to write it?
If you were to rewrite your SQL query as
select *
from People AS PE
JOIN
Sellers AS Sellers
ON PE.PersonGuid = SE.PersonGuid
WHERE
SE.Guid = ''
then the LINQ becomes a little more obvious:
var person = (from p in loginContext.Person
join s in loginContext.Seller on p.PersonGuid equals s.PersonGuid
where s.guid == ""
select p)
.FirstOrDefault();
Although your SQL would suggest FirstOrDefault is not required.
Warning - not tested.
You could probably try using LINQ JOIN clause.
It should be something like:
from p in loginContext.Person
join s in loginContext.Seller on new { p.PersonGuid = s.Guid }
where s.Guid = sellerGuid
You can know more about LINQ JOIN here -> Microsoft C# Reference
Hope it helps!
I will assume that select PersonGuid from Sellers where Guid = '' will provide a single value no matter what, otherwise your SQL statement would fail.
Accepted answer works, but if you want a solution that keeps the spirit of the SQL query, you can rely on Any function. Something along the following:
var person = await loginContext.Person
.Where(p => loginContext.Sellers
.Any(s => s.PersonGuid == p.PersonGuid && s.Guid = ""))
.FirstOrDefaultAsync();
I expect this to be transformed into an SELECT ... WHERE EXISTS which is similar to your initial query.

Convert SQL query to Linq (Need help to write sql query to linq)

This is my query returns me accurate result that I want. I want to write this in LINQ.
select i.reportdate,co.naam,i.issueid,i.vrijetekst,i.lockuser,te.teamnaam, count(ie.issueid) as events, sum(ie.bestedetijd) as Tijd
from company co,hoofdcontracten hc,subcontracten sc,sonderhoud so,g2issues i,g2issueevents ie, g2issuestatus iss,teams te,locatie l
Where co.companyid = hc.companyid And
hc.hcontractid = sc.hcontractid and
so.scontractid = sc.scontractid and
sc.scontractid = i.scontractid and
i.issueid = ie.issueid and
so.teamid = te.teamid and
ie.locatieid = l.locatieid and
l.bezoek = 0 and
i.issuestatusid = iss.issuestatusid and
fase < 7 and
co.companyid <> 165
group by i.reportdate,co.naam,i.issueid,i.vrijetekst,i.lockuser,te.teamnaam ,i.reportdate
having sum(ie.bestedetijd)>123
I am trying this but confused at select clause. How can I use aggregate function in select clause and group by clause also.
var myList = (from co in _context.Company
from hc in _context.Hoofdcontracten
from sc in _context.Subcontracten
from so in _context.Sonderhoud
from i in _context.G2issues
from ie in _context.G2issueEvents
from iss in _context.G2issueStatus
from te in _context.Teams
from l in _context.Locatie
where
co.CompanyId == hc.CompanyId
&& hc.HcontractId == sc.HcontractId
&& so.ScontractId == sc.ScontractId
&& sc.ScontractId == i.ScontractId
&& i.IssueId == ie.IssueId
&& so.Teamid == te.Teamid
&& ie.LocatieId == l.LocatieId
&& l.Bezoek == false
&& i.IssuestatusId == iss.IssueStatusId
&& iss.Fase < 7
&& co.CompanyId != 165
select new { }).ToList();
My! Someone was trying to save a few minutes typing time using all kinds of undecipherable variable names like hc, sc, so, i, ie, iss, without bothering that this would tenfold the time needed to understand what happens.
I haven't tried to decipher your query fully, apparently you thought it was not needed to show your entity framework classes and relation between them.
What I gather is that you want to perform a big join and then select several columns from the join. You want to group the resulting collection into groups of items that have the same reportdate, name, issueid, .... You want the bestede tijd on all items in each group, and you want the number of items in the group
LINQ has two types of syntaxes which in fact do the same: Query syntax and Method syntax. Although I can read the query syntax, I'm more familiar with the method syntax, so my answer will be using the method syntax. I guess you'll understand what happens.
I'll do it in smaller steps, you can concatenate all steps into one if you want.
First you do a big join, after which you select some columns. A big join is one of the few statements that are easier when written in query syntax (See SO: LINQ method syntax for multiple left join)
var bigJoin = from hc in _context.Hoofdcontracten
from sc in _context.Subcontracten
from so in _context.Sonderhoud
...
from l in _context.Locatie
where
co.CompanyId == hc.CompanyId
&& hc.HcontractId == sc.HcontractId
...
&& iss.Fase < 7
&& co.CompanyId != 165
select new
{
hc,
sc,
so,
... // etc select only items you'll use to create your end result
};
Now you've got a big table with the join results. You want to divide this table into groups with the same value for
{
i.reportdate,
co.naam,
i.issueid,
i.vrijetekst,
i.lockuser,
te.teamnaam,
}
(by the way: you mentioned reportdate twice in your GroupBy, I guess that's not what you meant)
This grouping is done using Enumerable.GroupBy.
var groups = bigjoin.GroupBy(joinedItem => new
{ // the items to group on: all elements in the group have the same values
ReportDate = i.Reportdate,
CompanyName = co.naam,
IssueId = i.issueid,
FreeText = i.vrijetekst,
LockUser = i.lockuser,
TeamName = te.teamnaam,
});
The result is a collection of groups. Each group contains the original bigJoin items that have the same value for for ReportDate, CompanyName, etc. This common value is in group.Key
Now from every group you want the following items:
Several of the common values in the group (ReportDate, CompanyName, IssueId, ...). We get them from the Key of the group
Tijd = the sum of ie.bestedeTijd of all elements in the group
Events = is the number of ie.IssueId of all elements in the group. As every element in the group has only one ie.IssueId, the result is also the number of elements in the group.
From this result, you only want to keep those with a Tijd > 123
So we'll do a new select on the groups, followed by a Where on Tijd
var result = groups.Select(group => new
{
Tijd = group.Sum(groupElement => groupElement.ie.bestedetijd),
Events = group.Count(),
// the other fields can be taken from the key
ReportDate = group.Key.reportdate,
CompanyName = group.Key.CompanyName,
IssueId = group.Key.Issueid,
FreeText = group.Key.FreeText,
LockUser = group.Key.LockUser,
TeamName = group.Key.TeamName,
})
.Where(resultItem => resultItem.Tijd > 123);

dynamic joincolumn and join clause with variables

I have a unidirectional one-to-one relation. what I want to achieve is either
create a join on clause using CASE and sending variables to it so that I can change the join column (any other suggestions to change join column are also welcomed)
ignore the first clause created by hibernate and create specific ON clause using criteria as in the bottom of the question.
My query that works in SQL Manager:
SELECT d.Description, d.AccountId, d.PartnerId, a.FormattedName FROM
InvoiceDesigns d
INNER JOIN UserAccount a
ON
a.id =
CASE
WHEN d.PartnerId != -1 AND d.AccountId = 1 THEN d.PartnerId
WHEN d.PartnerId = 1 THEN d.AccountId
ELSE 1
END
WHERE
d.AccountId = 1 OR PartnerId = 1
The working query runs in SQL Manager, not in hibernate. it is the thing I want to achieve, I want hibernate to create a query like this.
When an account, lets say ID = 1 to stick with example, displays designs they will see
A) PartnerId of design is NOT -1 AND AccountId is 1, which means it is created by a partner for that account and will display partner's formattedName.
This is for normal accounts when viewing their avaiable designs. If a partner created a design for them, they will see that partner's name.
B) PartnerId equals to ID of the account, which means it is a Partner design and formattedName of the account related design is created for will be displayed.
This is for partner accounts to view designs they created.
C) Design is created by the account directly, so will display its own name. For normal accounts and self created designs.
Normal join is working fine in hibernate when I want to use only the AccountId or PartnerId but I need to change the column to get the related FormattedName. I tried #Where, #Filter, #JoinColumnOrFormula but I wasn't able to achieve this query.
My classes with #Where basically look like this:
Design class:
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
int id;
#Column(name="AccountId")
int accountId;
#Column(name="PartnerId")
int partnerId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "AccountId", referencedColumnName = "id", insertable = false, updatable = false, nullable = true)
#Where(clause = "CASE " +
"WHEN PartnerId != -1 AND AccountId = :accountId THEN PartnerId " +
"WHEN PartnerId = :accountId THEN AccountId " +
"ELSE :accountId " +
"END")
AccountData account;
Account class:
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
int id;
#Column(name="FormattedName")
String formattedName;
Here you can see that I want to send an ID to query as parameter using ":accountId" which will be used to define the join column.
this question: custom join clause actually does something similar. That is why I focused on where clause
Addition: I also found something with criteria
Criteria criteria = session.createCriteria(InvoiceDesignData.class, "design");
criteria.createCriteria("design.account", "acc", JoinType.LEFT_OUTER_JOIN, Restrictions.eqProperty("acc.id", "design.partnerId"));
invoiceDesignList = criteria.list();
But this does not ignore the first condition and adds an additional condition to ON clause using AND. I removed join columns from #one-to-many hoping to ignore first condition but it does not. Produced query:
from InvoiceDesigns this_ left outer join UserAccount acc1_ on this_.account_id=acc1_.id and ( acc1_.id=this_.PartnerId )
Thanks in advance
You can try moving the case statement:
SELECT d.Description, d.AccountId, d.PartnerId, a.FormattedName
FROM (SELECT *, CASE
WHEN PartnerId != -1 AND AccountId = 1 THEN PartnerId
WHEN PartnerId = 1 THEN AccountId
ELSE 1
END AS SmartID
FROM InvoiceDesigns) d
INNER JOIN UserAccount a
ON a.id = d.SmartID
WHERE d.AccountId = 1 OR PartnerId = 1
EDIT: This basically leaves you with a straightforward join with an in-line query without a conditional ON statement.
If you only want to map each Design to a single AccountData object, with a single join, but you want to join on either the accountId or partnerId depending on their values, then you should be able to do this by using a join formula. You'll want something like:
#ManyToOne
#JoinColumnsOrFormulas(
{ #JoinColumnOrFormula(
formula = #JoinFormula(
value = "case " +
"when partnerId != -1 and accountId = 1 then partnerId" +
"when partnerId = accountId then accountId " +
"else 1" +
"end",
referencedColumnName="id")) }
)
Note that you need to use the #ManyToOne annotation, even though this is really a one to one association. The join formula won't work if you try to use it on a one to one.
Then you can simply retrieve the data with a normal HQL join:
SELECT d.description, d.accountId, d.partnerId, a.formattedName FROM Design d
inner join d.account a

Entity Framework left outer join not working

I have an Entity Framework model with the following classes (I have simplified the classes for simpler viewing):
PuzzleItem
-PuzzleId (int, primary key)
-Title
PuzzleProgress
-ProgressId (int, primary key)
-PuzzleId (FK)
-UserId (FK)
In PuzzleItem I have a number of levels. The PuzzleProgress keeps track of which level the user is at by inserting a record of that level when the user completes a previous level. For a start a new user will have one entry in PuzzleProgress with PuzzleId = 1.
In my code, I am using the following statement to perform a left outer join, so that I will get a list of all puzzles, and indicating to me which puzzle has not been solved. I made reference to this post from StackOverflow.
Here is my code:
var result = from pzs in e.PuzzleItems
join prg in e.PuzzleProgresses on pzs equals prg.PuzzleItem
into pzs_prg_tbl
from pzs_prg in pzs_prg_tbl.DefaultIfEmpty()
where pzs_prg.UserId == userId
select new SimplePuzzleItem()
{
PuzzleId = pzs_prg.PuzzleId,
PuzzleName = pzs_prg.PuzzleItem.Title,
IsUnlocked = (pzs_prg == null?false:true)
};
After running the code, only the first level of this new user is returned (while the PuzzleItem table has 3 records).
I've tried playing around with the code but the one I pasted above is the nearest I can get to, can anybody point me in the correct direction? Thanks!
It's a bit hard to say exactly without seeing more of the code, but where pzs_prg.UserId == userId is probably negating the left outer join.
What I mean is, if you're intending PuzzleItems LEFT JOIN PuzzleProgress then you want all PuzzleItems even when there is no PuzzleProgress. But the where pzs_prg.UserId == userId means PuzzleProgress cannot be null, because it must have a UserId (of value userId). So, you effectively have an inner join.
Personally, I don't like the "correct" way of doing joins (left or inner) in linq, so this is how I would correct the linq statement:
var result = from pz in db.PuzzleItems
from pg in db.PuzzleProgresses
.Where(pg => pg.PuzzleId == pz.PuzzleId)
.Where(pg => pg.UserId == userId)
.DefaultIfEmpty()
select new
{
PuzzleId = pz.PuzzleId,
PuzzleName = pz.Title,
IsUnlocked = (pg != null)
};
This reads a lot more like SQL joins, which I learned a long time ago so it fits my thinking.
If you want to re-factor the join type syntax, look at this 'LINQ Joining in c# with multiple conditions'
I think the where clause is filtering out the records. You need to include the where clause in your left join. Like so:
var result = from pzs in e.PuzzleItems
join prg in e.PuzzleProgresses on new { pzs.PuzzleId, UserId = userId } equals new { prg.PuzzleId, prg.UserId }
into pzs_prg_tbl
from pzs_prg in pzs_prg_tbl.DefaultIfEmpty()
select new SimplePuzzleItem()
{
PuzzleId = pzs_prg.PuzzleId,
PuzzleName = pzs_prg.PuzzleItem.Title,
IsUnlocked = (pzs_prg == null?false:true)
};

How can i do this SQL in Linq? (Left outer join w/ dates)

My LINQ isnt the best, neither is my SQL but im trying to do something like this in LINQ (its kind of in pseudo-code)
select * from CarePlan c
-- only newest Referral based on r.Date (if more than one exists)
left outer join Referral r on r.CarePlanId = c.CarePlanId
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
The data looks like this:
A Patient can have a bunch of careplans
each careplan can have 0 to n referrals (I want the newest referral per careplan - if any exist)
Would like to return a list of careplans for each patient (whether or not they have a referral or not, if it has more than one referral - grab the newest one)
Thanks for any help guys
In LINQ you use the DefaultIfEmpty to achieve a left outer join - examples at http://msdn.microsoft.com/en-us/library/bb397895.aspx
Assuming that the referrals are not a (potentially empty) collection on the care plans so you're joining two collections together ...
Your query it would be something like:
Get the latest referral per Care Plan:
var latestReferrals = from r in referrals
group r by r.CarePlanId into lr
select new { CarePlanId = lr.Key, LatestReferral = lr.OrderByDescending(lrd => lrd.Date).First()};
Find the combined details:
var q = from c in CarePlan
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
join lr in latestReferrals on c.CarePlanId equals lr.CarePlanId into gj
from subReferral in gj.DefaultIfEmpty()
select new { CarePlanId = c.CarePlanId, LatestReferral = (subReferral == null) ? null : subReferral.LatestReferral };
Depending on whether you want many referral properties or just a few you may or may not want the whole Referral object in the second part or just extract the relevant properties.
You may be able to combine these into a single query but for readability it may be easier to keep them separate. If there is a combined solution you should also compare performance of the two approaches.
EDIT: see comment for joining patients/other tables from care plans
If Patient is joined from Referral (as per comment) then its more complex because you're doing several left outer joins. So switching to the slightly more concise syntax:
var combined = from c in carePlans
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
from lr in latestReferral.Where(r => r.CarePlanId == c.CarePlanId).DefaultIfEmpty()
from p in patients.Where(patient => patient.PatientId == ((lr != null) ? lr.LatestReferral.PatientId : -1)).DefaultIfEmpty()
select new { c.CarePlanId, PatientName = (p == null) ? "no patient" : p.PatientName, LatestReferral = (lr == null) ? null : lr.LatestReferral };