QueryOver - adding additional criteria to a Join - c#

I'm quite new to NHibernate and QueryOver and I can't get NHibernate to generate the SQL I need.
I need to make a join and have an extra criteria on so I avoid getting to much data from the table I'm joining with.
The SQL I receive from QueryOver is:
SELECT * FROM adresse this_
left outer join r580_test.afvigelse remarkalia1_ on this_.id=remarkalia1_.adrid
left outer join r580_test.afvigelseklagepunkter remarkcomp5_ on remarkalia1_.id=remarkcomp5_.afvigelseid
left outer join r580_test.klagepunkter complainta2_ on remarkcomp5_.klagepunktid=complainta2_.id
WHERE this_.id = 16633 and remarkalia1_.dato between '2009-03-13 00:00:00' and '02-03-2012 16:34:35'
What I would like is this(the where date between has been moved to the end for the first left outer join):
SELECT * FROM adresse this_
left outer join r580_test.afvigelse remarkalia1_ on this_.id=remarkalia1_.adrid and remarkalia1_.dato between '2009-03-13 00:00:00' and '02-03-2012 16:34:35'
left outer join r580_test.afvigelseklagepunkter remarkcomp5_ on remarkalia1_.id=remarkcomp5_.afvigelseid
left outer join r580_test.klagepunkter complainta2_ on remarkcomp5_.klagepunktid=complainta2_.id
WHERE this_.id = 16633
My QueryOver looks like this:
adr = session.QueryOver<Address>()
.Where(x => x.Id == 16633)
.JoinQueryOver<Remark>(y => y.Remarks).Where(y => y.Created > DateTime.Now.AddDays(-14))
.JoinAlias(y => y.RemarkComplaint, () => complaintAlias, JoinType.LeftOuterJoin)
.SingleOrDefault();
Anyone got an idea about how to fix this?

There are several overloads for joinqueryover - I believe you want something like:
Remark remark = null;
adr = session.QueryOver<Address>()
.Where(x => x.Id == 16633)
.JoinQueryOver<Remark>(y => y.Remarks, () => remark, y => y.Created > DateTime.Now.AddDays(-14))
.JoinAlias(y => y.RemarkComplaint, () => complaintAlias, JoinType.LeftOuterJoin)
.SingleOrDefault();
In this case the third parameter is the withClause which, I believe, will add the restriction to the join.

Related

SQL to Linq Lambda

Does anybody know how to convert this on outerjoin on LINQ Lambda?
I wan to achieve this using lambda linq
SELECT * FROM Posts as A LEFT JOIN Reactions as B on A.Id = B.PostId AND #userId = b.userid
Here is my current linq code
return await _dbContext.Posts
.GroupJoin(_dbContext.Reactions,
post => post.Id, reaction => reaction.PostId,
(post, reactions) => new { post, reactions })
.SelectMany(x => x.reactions.DefaultIfEmpty(),
(post, reaction) => new { post.post, reaction })
What you want to accomplish can be done in two different ways in SQL, and those ways can be translated to Linq.
Depending on your scenario (volume of data, indexes, etc) you may want to need one or another
Option A: Join the filtered data
SELECT a.Name, b.*
FROM
tableA
LEFT JOIN tableB on
b.Action='delete' AND a.Id = b.Id
would be translated in LINQ to something similar to:
var query =
from a in db.TableA
join pet in db.TableB.Where(x => x.Action=="delete") on a equals b.TableA into gj
from leftJoined in gj.DefaultIfEmpty()
and using method syntax:
var query = tableA
.GroupJoin(
tableB.Where(x => x.Action == "delete"),
tableA => tableA,
tableB => tableB.tableA,
(tableA, tableBs) => new {tableA, tableBs}
).SelectMany(x => x.tableBs.DefaultIfEmpty())
Option B: Do the join and later filter the data
SELECT a.Name, b.*
FROM
tableA
LEFT JOIN tableB on a.Id = b.Id
WHERE
b.Id = NULL OR b.Action='delete'
would be translated to:
var query =
from a in db.TableA
join pet in db.TableB on a equals b.TableA into gj
from leftJoined in gj.DefaultIfEmpty()
where lefjoined == null || leftjoined.Action == "delete"
A left outer join is a join in which each element of the first collection is returned, regardless of whether it has any correlated elements in the second collection. You can use LINQ to perform a left outer join by calling the DefaultIfEmpty method on the results of a group join.
You can use this approach
Query Syntax:
var query = (from post in Posts
join reaction in Reactions
on post.Id equals reaction.PostId
into reaction
from reaction in reaction.DefaultIfEmpty()
select new
{
post.Id,
//prod.Foo1,
//post.Foo2,
//reaction.Foo3,
//reaction.Foo4,
//you can select other fields too
}).OrderBy(ps => ps.Id);
For more information visit Perform left outer joins
Normally you don't. Flattening out related data like that is simply not necessary in LINQ. Just fetch the data with its natural shape:
_dbContext.Posts.Include(p => p.Reactions)
This returns the Posts and any reactions, without having to repeat the Post data for each Reaction, or having nulls for Posts without Reactions.

Linq Query With Multiple Joins Not Giving Correct Results

I have a Linq query which is being used to replace a database function. This is the first one with multiple joins and I can't seem to figure out why it returns 0 results.
If you can see any difference which could result in the incorrect return it would be greatly appreciated......I've been trying to solve it longer than I should have.
Linq Query
context.StorageAreaRacks
.Join(context.StorageAreas, sar => sar.StorageAreaId, sa => sa.Id, (sar, sa) => new { sar, sa })
.Join(context.StorageAreaTypes, xsar => xsar.sar.StorageAreaId, sat => sat.Id, (xsar, sat) => new { xsar, sat })
.Join(context.Racks, xxsar => xxsar.xsar.sar.RackId, r => r.Id, (xxsar, r) => new { xxsar, r })
.Where(x => x.xxsar.sat.IsManual == false)
.Where(x => x.r.IsEnabled == true)
.Where(x => x.r.IsVirtual == false)
.Select(x => new { x.xxsar.sat.Id, x.xxsar.sat.Name })
.Distinct()
.ToList();
This is the query which is generated by the LINQ query
SELECT
[Distinct1].[C1] AS [C1],
[Distinct1].[Id] AS [Id],
[Distinct1].[Name] AS [Name]
FROM ( SELECT DISTINCT
[Extent2].[Id] AS [Id],
[Extent2].[Name] AS [Name],
1 AS [C1]
FROM [dbo].[StorageAreaRacks] AS [Extent1]
INNER JOIN [dbo].[StorageAreaTypes] AS [Extent2] ON [Extent1].[StorageAreaId] = [Extent2].[Id]
INNER JOIN [dbo].[Racks] AS [Extent3] ON [Extent1].[RackId] = [Extent3].[Id]
WHERE (0 = [Extent2].[IsManual]) AND (1 = [Extent3].[IsEnabled]) AND (0 = [Extent3].[IsVirtual])
) AS [Distinct1]
Sql Query which produces required results
SELECT DISTINCT sat.Name, sat.Id
FROM StorageAreaRacks sar
JOIN StorageAreas sa on sa.id = sar.StorageAreaId
JOIN StorageAreaTypes sat on sat.id = sa.StorageAreaTypeId
JOIN Racks r on r.id = sar.RackId
WHERE sat.IsManual = 0
AND r.IsEnabled = 1
AND r.IsVirtual = 0
Using joins with LINQ method syntax is hard to read and error prone.
Using joins with LINQ query syntax is better, but still error prone (you can join by the wrong key as you did) and does not give you information about join cardinality.
The best for LINQ to Entities queries is to use navigation properties (as Gert Arnold suggested in the comments and not only - see Don’t use Linq’s Join. Navigate!) because they have none of the aforementioned drawbacks.
The whole query should be something like this:
var query = context.StorageAreaRacks
.Where(sar => !sar.StorageArea.StorageAreaType.IsManual
&& sar.Rack.IsEnabled && !sar.Rack.IsVirtual)
.Select(sar => new
{
sar.StorageArea.StorageAreaType.Id,
sar.StorageArea.StorageAreaType.Name,
})
.Distinct();
or
var query = (
from sar in context.StorageAreaRacks
let sat = sar.StorageArea.StorageAreaType
let r = sar.Rack
where !sat.IsManual && r.IsEnabled && !r.IsVirtual
select new { sat.Id, sat.Name })
.Distinct();
Simple, readable and almost no place for mistakes. Navigation properties are one of the most beautiful features of EF, don't miss them.
Your LINQ doesn't translate the SQL properly; it Joins the StorageAreaTypes on the StorageAreaRack.StorageAreaId instead of on the StorageAreas.StorageAreaTypeId, which is why EF drops the StorageAreas Join - it has no effect on the outcome.
I think it is clearer if you elevate the members of each join to flatten the anonymous objects and name them based on their members (that are the join tables). Also, no reason to separate the Where clauses, LINQ can use && as well as SQL using AND. Also, if you have boolean values, don't compare them to true or false. Also there is no reason to pass range variables through that aren't used later.
Putting it all together:
var ans = context.StorageAreaRacks
.Join(context.StorageAreas, sar => sar.StorageAreaId, sa => sa.Id, (sar, sa) => new { sar, sa })
.Join(context.StorageAreaTypes, sarsa => sarsa.sa.StorageAreaTypeId, sat => sat.Id, (sarsa, sat) => new { sarsa.sar, sat })
.Join(context.Racks, sarsat => sarsat.sar.RackId, r => r.Id, (sarsat, r) => new { sarsat.sat, r })
.Where(satr => !satr.sat.IsManual && satr.r.IsEnabled && !satr.r.IsVirtual)
.Select(satr => new { satr.sat.Id, satr.sat.Name })
.Distinct()
.ToList();
However, I think when multiple joins are involved and when translating SQL, LINQ comprehension syntax can be easier to understand:
var ans = (from sar in context.StorageAreaRacks
join sa in context.StorageAreas on sar.StorageAreaId equals sa.Id
join sat in context.StorageAreaTypes on sa.StorageAreaTypeId equals sat.Id
join r in context.Racks on sar.RackId equals r.Id
where !sat.IsManual && r.IsEnabled && !r.IsVirtual
select new {
sat.Name,
sat.Id
}).Distinct().ToList();
You are missing a Where for your rack ID != null in your LINQ statement, and a Distinct().

LINQ lambda - OrderByDescending is adding undesired select

I'm working with a pretty wild lambda query. Here is my initial LINQ lambda statement (not being sorted/ordered by):
var query = orders.Join(customers, o => o.CustomerID, c => c.ID, (o, c) => new { o, c })
.Join(ordersections, o => o.o.ID, os => os.OrderID, (o, os) => new { o.o, o.c, os })
.Join(tickets, o => o.os.ID, t => t.OrderSectionID, (o, t) => new { o.o, o.c, o.os, t })
.Join(events, o => o.t.EventID, e => e.id, (o, e) => new { o.o, o.c, o.os, o.t, e })
.Join(clients, o => o.e.ClientID, cl => cl.id, (o, cl) => new { o.o, o.c, o.os, o.t, o.e, cl })
.Join(venues, o => o.e.VenueID, v => v.VenueID, (o, v) => new ModelsCs.LINQ.CustomerSearchResult { order = o.o, customer = o.c, orderSection = o.os, ticket = o.t, evt = o.e, client = o.cl, venue = v })
.AsExpandable()
.Where(predicate) // from PredicateBuilder
.GroupBy(x => new
{
// variables to group by
})
.Select(s => new CustomerSearchResult
{
// Selecting the variables, all good and fun!
});
The SQL that is generated is as follows:
SELECT <correct variables to select>
FROM [dbo].[Order] AS [t0]
INNER JOIN [dbo].[Customer] AS [t1] ON [t0].[Customer] = ([t1].[Customer])
INNER JOIN [dbo].[OrderSection] AS [t2] ON [t0].[Order] = [t2].[Order]
INNER JOIN [dbo].[Ticket] AS [t3] ON [t2].[OrderSection] = [t3].[OrderSection]
INNER JOIN [dbo].[Event] AS [t4] ON [t3].[Event] = [t4].[Event]
INNER JOIN [dbo].[Client] AS [t5] ON [t4].[Client] = ([t5].[Client])
INNER JOIN [dbo].[Venue] AS [t6] ON [t4].[Venue] = ([t6].[Venue])
WHERE ([t5].[Brand] = #p0)
AND ([t0].[Brand] = #p1)
AND ([t4].[EventStart] >= #p2)
AND ([t0].[OrderDateTime] >= #p3)
AND ([t1].[email] LIKE #p4)
GROUP BY <correct group by variables>
Beautiful! But I need to order the results, so I also want this at the end:
...
ORDER BY SortingVariable1 desc
(^^^^ THIS IS WHAT I'M TRYING TO DO)
Here is what I have already tried:
So I tried adding this to my LINQ lambda statement:
.OrderByDescending(x => x.SortingVariable1)
But this is now the SQL code that is generated:
SELECT <correct variables to select>
FROM (
SELECT <correct GROUP BY variables>
FROM [dbo].[Order] AS [t0]
INNER JOIN [dbo].[Customer] AS [t1] ON [t0].[Customer] = ([t1].[Customer])
INNER JOIN [dbo].[OrderSection] AS [t2] ON [t0].[Order] = [t2].[Order]
INNER JOIN [dbo].[Ticket] AS [t3] ON [t2].[OrderSection] = [t3].[OrderSection]
INNER JOIN [dbo].[Event] AS [t4] ON [t3].[Event] = [t4].[Event]
INNER JOIN [dbo].[Client] AS [t5] ON [t4].[Client] = ([t5].[Client])
INNER JOIN [dbo].[Venue] AS [t6] ON [t4].[Venue] = ([t6].[Venue])
WHERE ([t5].[Brand] = #p0)
AND ([t0].[Brand] = #p1)
AND ([t4].[EventStart] >= #p2)
AND ([t0].[OrderDateTime] >= #p3)
AND ([t1].[email] LIKE #p4)
GROUP BY <correct group by variables>
) AS [t7]
ORDER BY [t7].[SortingVariable1] DESC
No matter where in my lambda statement I put that .OrderByDescending, it doesn't work correctly.
My question: Does anyone know how I can alter my LINQ Lambda statement to correctly add an ORDER BY SortingVariable1 DESC to the end of the generated SQL statement?
The outer SELECT by itself is not a problem, because it does not come with an additional overhead of descernable magnitude. The addition of nesting allows SQL generator do sorting on any of the returned fields, even the calculated ones, without including the computation twice.
This behavior is due to a limitation of SQL illustrated by the example below:
SELECT A+B as A_plus_B
FROM MyTable
ORDER BY A_plus_B -- <=== This does not work
The query above must be re-written either with the computation repeated twice, i.e.
SELECT A+B as A_plus_B
FROM MyTable
ORDER BY A+B -- <=== Computation is repeated
or with a nested query or a CTE:
SELECT A_plusB FROM (
SELECT A+B as A_plus_B
FROM MyTable
)
ORDER BY A_plus_B -- <=== This works
LINQ's SQL generator takes the second approach, producing the statement that you see.
It is correctly adding the Order By. It is in the nature of auto-generated code that it is often not going to be as pretty as human generated code. It'll often be more verbose in what it writes, simply because generating such code is often easier.
If you want to have exactly a certain set of SQL code, you'll need to write it by hand. If you want to let it be automatically generated for you then you'll have to be satisfied with less pretty but perfectly correct and equally functional code.

optimize Entity Framework Linq query (selected 1 unexpected field)

All,
Can anyone help me optimize the following EF/Linq query:
The EF/Linq query (taken from LinqPad):
Articles
.AsNoTracking()
.Where(a => a.Active == "J")
.SelectMany(a => KerlServices
.Where(ks => ks.Service.SAPProductNumber == a.SAPProductNumber))
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.ToList()
The relation between Articles and Services (ks.Service.SAPProductNumber == a.SAPProductNumber) is in theory a 1:optional relation with cannot be defined in EF. This is however not my question.
The resulting SQL query:
SELECT
[Join1].[F_SERVICESID] AS [F_SERVICESID],
[Join1].[F_KERLCOD] AS [F_KERLCOD],
[Join1].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Join1].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_ART] AS [Extent1]
INNER JOIN (SELECT [Extent2].[F_KERLCOD] AS [F_KERLCOD], [Extent2].[F_SERVICESID] AS [F_SERVICESID], [Extent3].[F_SAPARTNUM] AS [F_SAPARTNUM], [Extent3].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent2]
INNER JOIN [dbo].[T_SERVICES] AS [Extent3] ON [Extent2].[F_SERVICESID] = [Extent3].[F_ID] ) AS [Join1] ON [Extent1].[F_SAPARTNUM] = [Join1].[F_SAPARTNUM]
WHERE N'J' = [Extent1].[F_ACTIND]
Why does EF generate a query that selects [Join1].[F_SERVICESID]? I don't need this field. Does anyone know a way to prevent this?
Kind regards, Jan.
ADDITION 1:
KerlServices
.AsNoTracking()
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.Join(
Articles,
ks => ks.SAPProductNumber,
a => a.SAPProductNumber,
(ks, a) => new { ks, a.Active })
.Where(ksa => ksa.Active == "J")
.Select(ksa => ksa.ks)
.ToList()
results in:
SELECT
[Extent1].[F_SERVICESID] AS [F_SERVICESID],
[Extent1].[F_KERLCOD] AS [F_KERLCOD],
[Extent2].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Extent2].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent1]
INNER JOIN [dbo].[T_SERVICES] AS [Extent2] ON [Extent1].[F_SERVICESID] = [Extent2].[F_ID]
INNER JOIN [dbo].[T_ART] AS [Extent3] ON [Extent2].[F_SAPARTNUM] = [Extent3].[F_SAPARTNUM]
WHERE N'J' = [Extent3].[F_ACTIND]
This 'improvement' does not answer my own question, but the result surely looks prettier to me.
UPDATE 1:
The query in Ivan Stoev's answer produces the following SQL:
SELECT
[Extent1].[F_SERVICESID] AS [F_SERVICESID],
[Extent1].[F_KERLCOD] AS [F_KERLCOD],
[Extent2].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Extent2].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent1]
INNER JOIN [dbo].[T_SERVICES] AS [Extent2] ON [Extent1].[F_SERVICESID] = [Extent2].[F_ID]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[T_ART] AS [Extent3]
WHERE (N'J' = [Extent3].[F_ACTIND]) AND ([Extent3].[F_SAPARTNUM] = [Extent2].[F_SAPARTNUM])
)
Why does EF generate a query that selects [Join1].[F_SERVICESID]? I don't need this field.
That's weird if true, I have no explanation for that.
Can anyone help me optimize the following EF/Linq query
It's worth trying the following, which for me represents the most logical way to retrieve the data in question:
KerlServices
.AsNoTracking()
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.Where(ks => Articles.Any(a => a.Active == "J" && a.SAPProductNumber == ks.SAPProductNumber)
.ToList()
UPDATE: Recently I've encountered that EF includes some additional fields in the generated SQL query when dialing with foreign key relations. These fields are not included in the projected result, so I think you should not worry about. Take any of the queries above, execute it inside the real code environment (VS Debug) and check the the projected list - I'm pretty sure the field in question will not be there.

Multiple Left Outer Join with lambda expressions

I have an SQL Query to do with Lambda Expressions like this, generally with more joins than in this example.
select Table2.a,
Table2.b,
Table2.c,
Table2.d
from Table1
LEFT OUTER JOIN Table2
ON Table2.a = Table1.a and
Table2.b = Table1.b and
Table2.c = Table1.c
LEFT OUTER JOIN Table3
ON Table3.b = Table1.b AND
Table3.c = Table1.c AND
Table3.d = Table1.d
where ( Table1.a = ValueA )
order by Table3.f
I'm doing this with Join() Lambda Expression, but i see in SQL Server profiler that this generate an INNER JOIN and i need a LEFT OUTER JOIN.
This is how i'm doing it with Join()
var RS = DBContext.Table1.Join(DBContext.Table2,
Table1 => new {Table1.a, Table1.b, Table1.c},
Table2 => new {Table1.a, Table1.b, Table1.c},
(Table1, Table2) => new {Table1})
.Join(DBContext.Table3,
LastJoin => new {LastJoin.Table1.b, LastJoin.Table1.c, LastJoin.Table1.d},
Table3 => new {Table3.b, Table3.c, Table3.d},
(LastJoin,Table3) => new {LastJoin.Table1, Table3})
.Where (LastTable => LastTable.Table1.a == ValueA)
.OrderBy(LastTable => LastTable.Table3.f)
.Select (LastTable => new {LastTable.Table1, LastTable.Table3});
I have been reading that it can be done with DefaultIfEmpty() or GroupJoin() but i haven't find any complex example with more than one LEFT OUTER JOIN.
Why don't you try using linq query, it is also much easier to write and understand both as compared to lambda expressions. I have on such implementation like:
var products =
from p in this.Products
from cat in this.ProductCategoryProducts
.Where(c => c.ProductID == p.ProductID).DefaultIfEmpty()
from pc in this.ProductCategories
.Where(pc => ac.ProductCategoryID == cat.ProductCategoryID).DefaultIfEmpty()
where p.ProductID == productID
select new
{
ProductID = p.ProductID,
Heading = p.Heading,
Category = pc.ProductCategory
};
return products ;

Categories